SAP Custom Workflow is not working (LPOR missing) - workflow

I created my first custom OO Workflow.
I created the class and added the interface IF_WORKFLOW.
I implemented the methods LPOR and FIND_BY_LPOR of interface BI_PERSISTENT.
The value of INSTID should be the Workitem ID.
I just have one activity (dialog) in my workflow. When I'm testing the workflow, an error appears.
Error-Code WL-821 - Dereferencing NULL-Reference.
I created a breakpoint in my custom FIND_BY_LPOR method. There is a CHECK for LPOR-INSTID is NOT INITIAL.
Unfortunately my LPOR-INSTID is always INITIAL. LPOR-CATID and LPOR-TYPEID are filled.
Is there something I have to customize?
Code:
CONSTRUCTOR (When is the constructor called, I can't get in while debugging?)
m_workitem_id = i_wi_id.
ms_lpor-catid = swfco_objtype_cl.
ms_lpor-typeid = mc_objtype.
ms_lpor-instid = i_wi_id.
LPOR
result = ms_lpor.
FIND_BY_LPOR
DATA: ls_key TYPE zs_wf_key,
ls_instance TYPE ty_instance.
CLEAR: ls_key, ls_instance.
CHECK lpor-instid IS NOT INITIAL. "HERE ITS EXITING, BECAUSE INSTID = INITIAL
READ TABLE mt_instances WITH KEY id = lpor-instid
INTO ls_instance.
IF sy-subrc NE 0.
TRY .
ls_key = lpor-instid.
CREATE OBJECT ls_instance-instance TYPE (lpor-typeid)
EXPORTING
is_key = ls_key.
CATCH cx_bo_error cx_root
RETURN.
ENDTRY.
ls_instance-id = lpor-instid.
APPEND ls_instance TO mt_instances.
ENDIF.
result ?= ls_instance-instance.

Related

How to modify double click on ALV event?

Hi guys!
I found a program on the internet that runs row details when I double-click the 'CARRID' cell on ALV.
How to rewrite this code so that the details are displayed no matter what cell in the table I click, not only in 'CARRID'?
CLASS lcl_handle_dc DEFINITION.
PUBLIC SECTION.
METHODS double_click
FOR EVENT double_click OF if_salv_gui_table_display_opt
IMPORTING ev_field_name eo_row_data.
ENDCLASS.
CLASS lcl_handle_dc IMPLEMENTATION.
METHOD double_click.
DATA: ls_sflight TYPE sflight.
CHECK ev_field_name = 'CARRID'.
* read the row data
eo_row_data->get_row_data(
EXPORTING
iv_request_type = if_salv_gui_selection_ida=>cs_request_type–all_fields
IMPORTING
es_row = ls_sflight ).
* Display the row data
cl_salv_ida_show_data_row=>display( iv_text = 'Flight Row Info' is_data = ls_sflight ).
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA: lr_salv TYPE REF TO if_salv_gui_table_ida,
lr_handle TYPE REF TO lcl_handle_dc.
cl_salv_gui_table_ida=>create(
EXPORTING
iv_table_name = 'SFLIGHT'
RECEIVING
ro_alv_gui_table_ida = lr_salv ).
DATA(lr_disp) = lr_salv->display_options( ).
* Enable double click
lr_disp->enable_double_click( ).
CREATE OBJECT lr_handle.
SET HANDLER lr_handle->double_click FOR ALL INSTANCES.
* Display ALV
lr_salv->fullscreen( )->display( ).
Remove the below line of code.
CHECK ev_field_name = 'CARRID'.
The CHECK statement ensures that the logical expression that follows evaluates to abap_true before continuing executing the next statements.

How to get updated HTML radio button in Workflow Inbox?

Since I'm so new for workflow, now I'm stuck at on how to get updated radio button of HTML table in Workflow inbox when click decision button.
STEP 1: ln HTML table, the user can approve or reject WBS by checked radio
STEP 2: When user click confirm, then the workflow will update status for each WBS that shown in the table.
Question:
On STEP 2 - how can I get the HTML text which updated by user action.
For now I've enhanced SAPEVENTS and using 'SAP_WAPI_READ_CONTAINER' to get HTML container.
However, after check the HTML code, the HTML table is not update to user action.
Here's a minimal program which demonstrates how to get the values of the radio buttons. If the user presses Approve on the first line and Reject on the second line, and presses the button Confirm, the method on_sapevent gets group[1]=approve&group[2]=reject.
(works in something like ABAP 7.40 SP8)
REPORT zdemo.
CLASS lcl_app DEFINITION.
PUBLIC SECTION.
METHODS at_selection_screen_output.
METHODS on_sapevent FOR EVENT sapevent OF cl_gui_html_viewer
IMPORTING action frame getdata postdata query_table.
PRIVATE SECTION.
DATA o_html TYPE REF TO cl_gui_html_viewer.
ENDCLASS.
CLASS lcl_app IMPLEMENTATION.
METHOD at_selection_screen_output.
DATA: l_url TYPE cndp_url,
l_text TYPE string.
IF o_html IS NOT BOUND.
l_text = '<body><form id="form" method="POST" action="SAPEVENT:submit">'
&& REDUCE string( INIT t = `` FOR I = 1 WHILE i <= 2
NEXT t = t && |<div><fieldset id="group{ i }">|
&& |<input type="radio" value="approve" name="group[{ i }]"> Approve |
&& |<input type="radio" value="reject" name="group[{ i }]"> Reject|
&& |</fieldset></div>| )
&& '<p><button type= "submit">Confirm</button></p>'
&& '</form></body>'.
o_html = NEW #( parent = cl_gui_container=>screen0 ).
SET HANDLER on_sapevent FOR o_html.
o_html->set_registered_events( events = value #( ( eventid = o_html->m_id_sapevent ) ) ).
DATA(lt_text) = cl_bcs_convert=>string_to_soli( l_text ).
o_html->load_data(
EXPORTING type = 'text' subtype = 'html' size = strlen( l_text )
IMPORTING assigned_url = l_url
CHANGING data_table = lt_text ).
o_html->show_url( EXPORTING url = l_url ).
ENDIF.
ENDMETHOD.
METHOD on_sapevent.
CONCATENATE LINES OF postdata INTO data(l_post_data) RESPECTING BLANKS.
MESSAGE l_post_data TYPE 'I'.
ENDMETHOD.
ENDCLASS.
PARAMETERS dummy.
DATA go_app TYPE REF TO lcl_app.
LOAD-OF-PROGRAM.
CREATE OBJECT go_app.
AT SELECTION-SCREEN OUTPUT.
go_app->at_selection_screen_output( ).

Get Line Items in an Invoice logic hook in SuiteCRM

Via a logic hook I'm trying to update fields of my products, after an invoice has been saved.
What I understand so far is, that I need to get the invoice related AOS_Products_Quotes and from there I could get the products, update the required fields and save the products. Does that sound about right?
The logic hook is being triggered but relationships won't load.
function decrement_stocks ( $bean, $event, $arguments) {
//$bean->product_value_c = $bean->$product_unit_price * $bean->product_qty;
$file = 'custom/modules/AOS_Invoices/decrement.txt';
// Get the Invoice ID:
$sInvoiceID = $bean->id;
$oInvoice = new AOS_Invoices();
$oInvoice->retrieve($sInvoiceID);
$oInvoice->load_relationship('aos_invoices_aos_product_quotes');
$aProductQuotes = $oInvoice->aos_invoices_aos_product_quotes->getBeans();
/*
$aLineItemslist = array();
foreach ($oInvoice->aos_invoices_aos_product_quotes->getBeans() as $lineitem) {
$aLineItemslist[$lineitem->id] = $lineitem;
}
*/
$sBean = var_export($bean, true);
$sInvoice = var_export($oInvoice, true);
$sProductQuotes = var_export($aProductQuotes, true);
$current = $sProductQuotes . "\n\n\n------\n\n\n" . $sInvoice . "\n\n\n------\n\n\n" . $sBean;
file_put_contents($file, $current);
}
The invoice is being retrieved just fine. But either load_relationship isn't doing anything ($sInvoice isn't changing with or without it) and $aProductQuotes is Null.
I'm working on SuiteCRM 7.8.3 and tried it on 7.9.1 as well without success. What am I doing wrong?
I'm not familiar with SuiteCRM specifics, however I'd always suggest to check:
Return value of retrieve(): bean or null?
If null, then no bean with the given ID was found.
In such case $oInvoice would stay empty (Your comment suggests that's not the case here though)
Return value of load_relationship(): true (success) or false (failure, check logs)
And I do wonder, why don't you use $bean?
Instead you seem to receive another copy/reference of $bean (and calling it $oInvoice)? Why?
Or did you mean to receive a different type bean that is somehow connected to $bean?
Then its surely doesn't have the same id as $bean, unless you specifically coded it that way.

Return a list of all atributes of a persistent class in abap

I got a persistent class on a custom table. Now with GET_PERSISTENT_BY_QUERY I can get a list of objects. How can I now get those Objects with all of their atributes printed to the user (e.g as ALV)?
Can I call an instance function if i click on it somehow?
Here is a dynamic solution, query_data is the result returned from the call to get_persistent_by_query( ). In the end the data will be stored in structured table <table>, you can use this to display in your ALV. Note that there is little control over the order of the field. For real use you'd have to add some error handling as well.
DATA: pers_obj_desc TYPE REF TO cl_abap_classdescr,
pers_query_data TYPE REF TO data,
field_cat TYPE lvc_t_fcat.
FIELD-SYMBOLS: <table> TYPE STANDARD TABLE.
LOOP AT query_data ASSIGNING FIELD-SYMBOL(<pers_data>).
AT FIRST.
pers_obj_desc ?= cl_abap_typedescr=>describe_by_object_ref( p_object_ref = <pers_data> ).
LOOP AT pers_obj_desc->attributes ASSIGNING FIELD-SYMBOL(<attr_desc>).
APPEND INITIAL LINE TO field_cat ASSIGNING FIELD-SYMBOL(<field_cat>).
<field_cat>-fieldname = <attr_desc>-name.
<field_cat>-intlen = <attr_desc>-length.
<field_cat>-datatype = <attr_desc>-type_kind.
ENDLOOP.
cl_alv_table_create=>create_dynamic_table(
EXPORTING
it_fieldcatalog = field_cat
IMPORTING
ep_table = pers_query_data
).
ASSIGN pers_query_data->* TO <table>.
ENDAT. " first
APPEND INITIAL LINE TO <table> ASSIGNING FIELD-SYMBOL(<line>).
LOOP AT pers_obj_desc->attributes ASSIGNING <attr_desc>.
ASSIGN COMPONENT <attr_desc>-name OF STRUCTURE <line> TO FIELD-SYMBOL(<field>).
data(getter) = 'GET_' && <attr_desc>-name.
CALL METHOD <pers_data>->(getter) RECEIVING result = <field>.
ENDLOOP.
ENDLOOP.
One (i do not think good) way is to define a instance atribute called list. Then, the generated getter can be overwritten as following, which is addresed from anywhere.
DATA: WA TYPE ZMM_SMATERIAL.
WA-ERNAM = GET_ERNAM( ).
wa-ERSDA = GET_ERSDA( ).
WA-LAEDA = GET_LAEDA( ).
WA-MATNR = GET_MATNR( ).
WA-MTART = GET_MTART( ).
.
.
.
result = WA.
" GET_LIST
endmethod.
The ... stands for each attribute I want in the List.
The CONS to this solution:
on every call there is very much logic to do
the attribute is not used
An other (I do not think good) way is to define a instance atribute called list. Then, on the initialize, the list gets prepared.
DATA: WA TYPE ZMM_SMATERIAL.
WA-ERNAM = GET_ERNAM( ).
wa-ERSDA = GET_ERSDA( ).
WA-LAEDA = GET_LAEDA( ).
WA-MATNR = GET_MATNR( ).
WA-MTART = GET_MTART( ).
.
.
.
LIST = WA.
endmethod.
The ... stands for each attribute I want in the List.
Then, on every setter, also in this list it gets changed
The CONS to this solution:
Everything is redundant,
each setter has to get changed
One more way is to define a instance atribute called list. Then, the generated getter can be overwritten as following, which is addresed from anywhere.
if me->list is initial.
DATA: WA TYPE ZMM_SMATERIAL.
WA-ERNAM = GET_ERNAM( ).
wa-ERSDA = GET_ERSDA( ).
WA-LAEDA = GET_LAEDA( ).
WA-MATNR = GET_MATNR( ).
WA-MTART = GET_MTART( ).
.
.
.
me->list = WA.
endif.
result = me->list
" GET_LIST
endmethod.
The ... stands for each attribute I want in the List.
Additionaly to that, I need to CLEAR the list on every setter.
The big con to that is that the generator overwrites everything. (Without any warning.)
Based on the answer of gert beukema I have written a helper class (with major improvements like accepting more than public simple atributes without failing).
class ZCL_PS_OBJECT_TO_STRUCTURE_HLP definition
public
create private .
public section.
class-methods REFTAB_TO_STRUCTURE_LIST
importing
!IT_REFRENCE_TABLE type OSREFTAB
returning
value(RR_OBJECT_ATTRIBUTES_TABLE) type ref to DATA .
class-methods CREATE_FIELDCAT
importing
!IR_OBJ_DESC type ref to CL_ABAP_CLASSDESCR
returning
value(RO_FIELD_CAT) type LVC_T_FCAT .
protected section.
private section.
ENDCLASS.
CLASS ZCL_PS_OBJECT_TO_STRUCTURE_HLP IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_PS_OBJECT_TO_STRUCTURE_HLP=>CREATE_FIELDCAT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IR_OBJ_DESC TYPE REF TO CL_ABAP_CLASSDESCR
* | [<-()] RO_FIELD_CAT TYPE LVC_T_FCAT
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD CREATE_FIELDCAT.
LOOP AT IR_obj_desc->attributes ASSIGNING FIELD-SYMBOL(<fs_attr_desc>).
APPEND INITIAL LINE TO ro_field_cat ASSIGNING FIELD-SYMBOL(<fs_field_cat>).
<fs_field_cat>-fieldname = <fs_attr_desc>-name.
<fs_field_cat>-intlen = <fs_attr_desc>-length.
<fs_field_cat>-datatype = <fs_attr_desc>-type_kind.
<fs_field_cat>-outputlen = calculate a good outputlength (double when hex)
ENDLOOP.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_PS_OBJECT_TO_STRUCTURE_HLP=>REFTAB_TO_STRUCTURE_LIST
* +-------------------------------------------------------------------------------------------------+
* | [--->] IT_REFRENCE_TABLE TYPE OSREFTAB
* | [<-()] RR_OBJECT_ATTRIBUTES_TABLE TYPE REF TO DATA
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD reftab_to_structure_list.
DATA: lr_obj_desc TYPE REF TO cl_abap_classdescr,
lr_query_data TYPE REF TO data,
lt_fieldcat TYPE lvc_t_fcat.
FIELD-SYMBOLS: <fs_table> TYPE STANDARD TABLE.
LOOP AT it_refrence_table ASSIGNING FIELD-SYMBOL(<fs_object>).
AT FIRST.
lr_obj_desc ?= cl_abap_typedescr=>describe_by_object_ref( p_object_ref = <fs_object> ).
lt_fieldcat = create_fieldcat( ir_obj_desc = lr_obj_desc ).
CALL METHOD cl_alv_table_create=>create_dynamic_table
EXPORTING
* i_style_table =
it_fieldcatalog = lt_fieldcat
* i_length_in_byte =
IMPORTING
ep_table = rr_object_attributes_table
* ep_table = lr_query_data
* e_style_fname =
EXCEPTIONS
generate_subpool_dir_full = 1
OTHERS = 2.
* IF sy-subrc <> 0.
* do some errorhandling here
* ENDIF.
ASSIGN rr_object_attributes_table->* TO <fs_table>.
* ASSIGN lr_query_data->* TO <fs_table>.
ENDAT. "first
APPEND INITIAL LINE TO <fs_table> ASSIGNING FIELD-SYMBOL(<fs_line>).
LOOP AT lr_obj_desc->attributes ASSIGNING FIELD-SYMBOL(<fs_attr_desc>).
ASSIGN COMPONENT <fs_attr_desc>-name OF STRUCTURE <fs_line> TO FIELD-SYMBOL(<fs_attr_value>).
DATA(getter) = 'GET_' && <fs_attr_desc>-name.
TRY .
CALL METHOD <fs_object>->(getter) RECEIVING result = <fs_attr_value>.
CATCH cx_sy_dyn_call_illegal_method. "get_method is protected or private
CLEAR <fs_attr_value>.
CATCH cx_sy_dyn_call_illegal_type.
"what to do with a list?
ENDTRY.
ENDLOOP.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
Now with that helperclass I do not have to recode it always I want a list out of a Object.
In a programm the call is e.G like this:
DATA: lt_query_data TYPE osreftab,
lt_query_table TYPE REF TO data.
FIELD-SYMBOLS <fs_table>.
lt_query_data = zca_ps_user=>agent->if_os_ca_persistency~get_persistent_by_query(
i_query = cl_os_system=>get_query_manager( )->create_query( )
).
CALL METHOD zcl_ps_object_to_structure_hlp=>reftab_to_structure_list
EXPORTING
it_refrence_table = lt_query_data
RECEIVING
rr_object_attributes_table = lt_query_table.
IF lt_query_table IS INITIAL.
LEAVE.
ENDIF.
ASSIGN lt_query_table->* TO <fs_table>.
*Create a ALV
DATA: lr_alv TYPE REF TO cl_salv_table .
cl_salv_table=>factory( IMPORTING r_salv_table = lr_alv
CHANGING t_table = <fs_table> )
.
lr_alv->display( ).
Ps: I wanted to edit the answer, but it was not accepted.

ABAP: Event CONTEXT_MENU_SELECT of CL_SIMPLE_TREE_MODEL - can't fire it

I have an instance of CL_SIMPLE_TREE_MODEL, I managed to trigger and handle the CONTEXT_MENU_REQUEST event, and I build my context menu.
I added my functions the way I wanted.
Problem is, when I select one of the options from my context menu, nothing happens. In other words, the program flow doesn't go inside the handler for CONTEXT_MENU_SELECT.
I'm of course assuming this event is fired when I click on a function in the context menu.
I found official documentation, but only for the "default context menu" that you access with Shift+F10, that needs certain subroutines in the program in order to fire.
What I did:
I did define and implement a method that is a handler for that event.
I did set the handler for the event at the same place I set the handler for the CONTEXT_MENU_REQUEST event
I did NOT register the event with the SET_REGISTERED_EVENTS because there is NO ID defined in the class attributes for that event - such as there was for the CONTEXT_MENU_REQUEST event.
The code:
REPORT.
CLASS lcl_tree_handler DEFINITION.
PUBLIC SECTION.
METHODS:
pbo,
on_ctx_menu_request FOR EVENT node_context_menu_request OF cl_simple_tree_model
IMPORTING node_key menu sender, "TYPE TM_NODEKEY CL_CTMENU
on_ctx_menu_select FOR EVENT node_context_menu_select OF cl_simple_tree_model
IMPORTING node_key fcode. "TYPE TM_NODEKEY SY-UCOMM
DATA:
po_tree_model TYPE REF TO cl_simple_tree_model,
gt_tree TYPE TABLE OF treemsnodt,
control TYPE REF TO cl_gui_control.
ENDCLASS.
CLASS lcl_tree_handler IMPLEMENTATION.
METHOD pbo.
DATA: lt_events TYPE cntl_simple_events,
ls_event TYPE cntl_simple_event.
FIELD-SYMBOLS <gs_tree> TYPE treemsnodt.
CHECK po_tree_model IS NOT BOUND.
CREATE OBJECT po_tree_model
EXPORTING
node_selection_mode = po_tree_model->node_sel_mode_single.
APPEND INITIAL LINE TO gt_tree ASSIGNING <gs_tree>.
<gs_tree>-node_key = 'Node key 1'.
<gs_tree>-text = 'First node'.
<gs_tree>-isfolder = 'X'.
APPEND INITIAL LINE TO gt_tree ASSIGNING <gs_tree>.
<gs_tree>-node_key = 'Node key 2'.
<gs_tree>-relatkey = 'Node key 1'.
<gs_tree>-relatship = cl_tree_model=>relat_last_child.
<gs_tree>-text = 'First child'.
po_tree_model->add_nodes(
node_table = gt_tree ).
ls_event-eventid = cl_simple_tree_model=>eventid_node_context_menu_req.
ls_event-appl_event = 'X'. "tried with space too
APPEND ls_event TO lt_events.
CALL METHOD po_tree_model->set_registered_events
EXPORTING
events = lt_events.
SET HANDLER on_ctx_menu_request FOR po_tree_model.
SET HANDLER on_ctx_menu_select FOR po_tree_model.
po_tree_model->create_tree_control(
EXPORTING
parent = cl_gui_container=>screen0
IMPORTING
control = control ).
ENDMETHOD.
METHOD on_ctx_menu_request. "I initialize the context menu object here.
DATA: lt_chidren_keys TYPE treemnotab,
ls_child_key TYPE tm_nodekey,
lv_text TYPE gui_text.
CALL METHOD sender->node_get_children
EXPORTING
node_key = node_key
IMPORTING
node_key_table = lt_chidren_keys
EXCEPTIONS
OTHERS = 2.
LOOP AT lt_chidren_keys INTO ls_child_key.
lv_text = ls_child_key.
CALL METHOD menu->add_function
EXPORTING
fcode = 'ONE'
text = lv_text
ftype = 'B'.
ENDLOOP.
menu->add_separator( ).
CALL METHOD menu->add_function
EXPORTING
fcode = 'ALL'
text = 'All the work groups'
ftype = 'W'.
ENDMETHOD.
METHOD on_ctx_menu_select.
BREAK-POINT. "tried actual code here too.
ENDMETHOD.
ENDCLASS.
DATA: go_tree_handler TYPE REF TO lcl_tree_handler.
PARAMETERS dummy.
INITIALIZATION.
CREATE OBJECT go_tree_handler.
AT SELECTION-SCREEN OUTPUT.
go_tree_handler->pbo( ).
AT SELECTION-SCREEN ON EXIT-COMMAND.
go_tree_handler->control->free( ).
The tree is displayed, on right click the context menu appears.
But nothing fires when I chose a menu item. Am I missing something ?
The function types you specify (ftype = 'W' and 'B') are not supported (check the fixed values of the underlying domain CUA_FUNTYP). In that case, nothing happens.
The classic solution is to use ftype = ' ' (normal function):
LOOP AT lt_chidren_keys INTO ls_child_key.
lv_text = ls_child_key.
CALL METHOD menu->add_function
EXPORTING
fcode = 'ONE'
text = lv_text
ftype = ' '.
ENDLOOP.
menu->add_separator( ).
CALL METHOD menu->add_function
EXPORTING
fcode = 'ALL'
text = 'All the work groups'
ftype = ' '.
The possible ftype values are (source: domain CUA_FUNTYP):
' ' : Normal function
'H' : Help function (PROCESS ON HELP REQUEST)
'S' : System function (handled directly by DYNP)
'T' : Transaction call (LEAVE TO TRANSACTION)
'E' : Access modules for 'AT EXIT COMMAND' -> /E as prefix
'I' : Include menu (replaced at runtime - not supported)
'N' : 'AT EXIT COMMAND' Function, > DYNP > /N as Prefix
Remark: the code in the first version of the question was missing the registering of the second event handler SET HANDLER go_tree_handler->on_ctx_menu_select FOR po_tree_model. (now it's okay)