How can I run ISPF Edit Macros in Batch - batch-processing

I frequently write and use ISPF edit macros. However, invoking them in foreground is time consuming. Can I use a Rexx program to run the edit macros against all, or a selection of, members of a PDS via batch?

You can use Library Management functions.
You use LMINIT to get a DATA ID for the dataset to be edited and then use the LMOPEN function to open the dataset.
You could then use LMMLIST if you want to perform the macro on a member or members of a PDS or PDSE.
You can then use the EDIT function specifying the macro to use/invoke, which should have an ISREDIT END or ISREDIT CANCEL.
If LMMLIST was used the list should be freed using LMMLIST with OPTION(FREE)
LMCLOSE should then be used to close the dataset.
LMFREE should then be used to free the DATA ID.
The above could be done in various programming languages, although REXX would probably be the simplest.

Here's an edit macro that will run another macro against all members of a PDS:
/*REXX - Edit macro to invoke the same macro against all members */
/* in the data set being edited. */
/* Syntax: */
/* ALLMEM macro prefix */
/* macro is the name of a macro to execute. If it */
/* is a program macro, remember to specify the */
/* exclamation point before the name. */
/* */
/* prefix is an optional prefix to use when selecting */
/* members to process. for example, ISR will */
/* process all members starting with ISR. */
/* */
/* Note that the macro which this calls can have an */
/* ISREDIT END or ISREDIT CANCEL in it to avoid the display */
/*------------------------------------------------------------------*/
Address 'ISPEXEC'
'ISREDIT MACRO (WORKMAC,PREFIX)'
'ISREDIT (DATA1) = DATAID'
'ISREDIT (THISONE) = MEMBER '
Address 'ISPEXEC' 'LMOPEN DATAID('data1') OPTION(INPUT)'
parse upper var prefix prefix .
member1=''
Do Until lmrc\=0
Address 'ISPEXEC' 'LMMLIST DATAID('data1') OPTION(LIST)',
'MEMBER(MEMBER1) STATS(YES)'
lmrc = rc
If lmrc = 0 ,/* if member name returned */
& member1\=thisone ,/* and it isn't this member */
& ( ,/* and prefix check is ok... */
prefix='' ,/* No prefix specified */
| substr(member1,1,length(prefix))=prefix,/* or prefix match*/
) Then
Do /* invoke edit with specified initial macro*/
Address 'ISPEXEC' 'CONTROL ERRORS CANCEL'
Address 'ISPEXEC' 'EDIT DATAID('data1')',
'MEMBER('member1') MACRO('workmac')'
Address 'ISPEXEC' 'CONTROL ERRORS CANCEL'
End
End
Address 'ISPEXEC' 'LMMLIST DATAID('data1') OPTION(FREE)'
Address 'ISPEXEC' 'LMCLOSE DATAID('data1')'
'ISREDIT DEFINE 'workmac' MACRO CMD'
If prefix='' ,/* No prefix specified */
| substr(thisone,1,length(prefix))=prefix, /* or prefix match*/
then
'ISREDIT 'workmac /* perform macro for this member */
It's for use under ISPF View or Edit, but could be made to work in batch, but you can also fire it off and sit back whilst it runs your macro against all of a PDS, saving you from having to run it on each member manually.

Related

Setting the value of an Enum field in a temporary record

Total newbie in AL here; I'm experimenting with automated testing in Dynamics BC for a new deployment. As a very basic test, I'd like to simply create a new Item record in the Cronus test database and validate each field. I'm running into trouble when I try to select the value for an enum field. Here's the code I'm using:
Procedure AddGoodItem()
// [Given] Good item data
var
recItem: Record Item Temporary;
Begin
recItem."Description" := 'zzzz';
recItem.validate("Description");
recItem.Type := recItem.Type::Inventory;
recItem.Validate(Type);
recItem."Base Unit of Measure" := 'EA';
recItem.Validate("Base Unit of Measure");
recItem."Item Category Code" := 'FURNITURE';
recItem.validate("Item Category Code");
End;
When I run this in Cronus, I get the error:
You cannot change the Type field on Item because at least one Item Journal Line exists for this item.
If I comment the Type lines out, the process runs successfully.
Given that this is a temporary record, it shouldn't have any Item Journal Lines, should it? What am I missing?
The code in the OnValidate trigger still runs, even if you have marked the Record variable as temporary. In addition temporary is not inherited to the underlying variables meaning any Record variables used in the OnValidate trigger are not temporary (unless marked as such).
There are two options:
Leave out the line recItem.Validate(Type);, if the code run by the OnValidate trigger is not relevant in this case.
Replace the line recItem.Validate(Type); with your own clone of the code from the OnValidate trigger and then remove the unneeded parts.

How to suppress dialog boxes coming from submitted program

I'm submitting a Z** program which is copy of the standard report of SAP (FBL5N). But there is a message saying that;
(count) archived documents were found
which is coming out from the logical database FBL5N uses. But I have to suppress this dialog box. I don't want is to be shown to user. (I shoudln't submit it as background job because I have to import the results from the FBL5N report).
I've tried
CALL FUNCTION 'DIALOG_SET_NO_DIALOG'
SUPPRESS DIALOG
etc.
Here is my submit;
SUBMIT /xyz/blablabla WITH so_wlbuk IN s_bukrs
WITH so_wlkun IN s_kunnr
WITH so_datex IN s_datex
WITH x_opsel = x_opsel
WITH pa_stida = pa_stida
WITH x_clsel = x_clsel
WITH pa_stid2 = pa_stid2
WITH x_aisel = x_aisel
WITH so_budat IN s_budat
WITH so_bldat IN s_bldat
WITH x_norm = x_norm
WITH x_shbv = x_shbv
WITH x_ters = x_ters
WITH x_denk = x_denk
WITH dd_bukrs IN s_bukrs
WITH x_apar = p_c_apar
WITH dd_kunnr IN s_kunnr
WITH so_konzs IN s_ckonzs
WITH s_umskz IN s_umskz
WITH s_blart IN s_blart
WITH so_gsber IN s_gsber
AND RETURN.
Any idea ?
I checked the code of this report and yes, the message is thrown both in GUI mode and when calling programmatically.
This LDB message is called deeply in standard code and cannot be suppressed (GD_COUNT_ARCH variable in SAPDBDDF report is checked).
The only solution I see here is calling transaction in batch mode. You ambiguously said you should (shouldn't?) do this in background, but nevertheless, BDC display mode is adjustable and if you want to avoid deep modifications of standard, this is your only solution.
The call can be organized just like that:
SET PARAMETER ID 'KUN' FIELD '1174'.
DATA: it_bdcdata TYPE TABLE OF bdcdata,
wa_bdcdata LIKE LINE OF it_bdcdata,
opt TYPE ctu_params.
CLEAR: wa_bdcdata.
wa_bdcdata-PROGRAM = 'RFITEMAR'.
wa_bdcdata-DYNPRO = '1000'.
wa_bdcdata-DYNBEGIN = 'X'.
APPEND wa_bdcdata TO it_bdcdata.
CLEAR: wa_bdcdata.
wa_bdcdata-fnam = 'X_AISEL'.
wa_bdcdata-fval = 'X'.
APPEND wa_bdcdata TO it_bdcdata.
CLEAR: wa_bdcdata.
wa_bdcdata-fnam = 'BDC_OKCODE'.
wa_bdcdata-fval = '=ONLI'.
APPEND wa_bdcdata TO it_bdcdata.
opt-dismode = 'E'.
CALL TRANSACTION 'FBL5N' USING it_bdcdata OPTIONS FROM opt.
Only one parameter was filled here (All items radiobutton in Line item selection section) and you can fill missing ones according this sample. I recommend you to use SPA/GPA parameteres for initial entry where possible as this is more compact and works faster than BDC table.

How to find a standard text within a SapScript or SmartForm?

I need to track down where within a large number of custom sapscripts and smartforms a specific standard text (SO10) is being used.
Apart from the equivalent of "check the code for each print script", I've not found a workable solution online. Any suggestions?
After posting, I found a partial solution. The code below will search for a standard text within sapscripts, but not smartforms.
PARAMETERS: p_sttxt LIKE stxh-tdname.
DATA: BEGIN OF t_stxh OCCURS 0,
tdname LIKE stxh-tdname,
tdspras LIKE stxh-tdspras,
END OF t_stxh.
DATA t_lines LIKE tline OCCURS 0 WITH HEADER LINE.
SELECT tdname tdspras FROM stxh INTO TABLE t_stxh
WHERE tdobject = 'FORM'
AND tdid = 'TXT'
AND tdspras = 'E'.
LOOP AT t_stxh.
REFRESH t_lines.
CALL FUNCTION 'READ_TEXT'
EXPORTING
* CLIENT = SY-MANDT
id = 'TXT'
language = t_stxh-tdspras
name = t_stxh-tdname
object = 'FORM'
TABLES
lines = t_lines
EXCEPTIONS
id = 0
language = 0
name = 0
not_found = 0
object = 0
reference_check = 0
wrong_access_to_archive = 0
OTHERS = 0 .
SEARCH t_lines FOR p_sttxt.
IF sy-subrc EQ 0.
WRITE:/ t_stxh-tdname, t_stxh-tdspras.
ENDIF.
ENDLOOP.
This is a (fixed) version of the code found here: http://scn.sap.com/thread/179142
What concerns SmartForms, you cannot. You cannot just find it like you want it.
Unfortunately, in such ̶g̶o̶o̶d̶ ̶o̶l̶'̶ legacy technology as SmartForms everything is working legacy way, and standard texts are simply hard-coded. Yes, it looks awkward but they are really hard-coded, and these names are written out to SmartForm FM code every time it is re-generated.
So the only workaround here is to analyze the code.
Find all FMs for existing Smart Forms in system
There is a D010INC table containing all forms with their includes. The main point here is that all SmartForm FMs start with /1BCDWB/ prefix.
The main logic is in the includes, so we need to find correspondent INCLUDE for the target form.
Fetch SF include source code
It can be done in a several ways: via CL_RECA_RS_SERVICES class, via table REPOSRC, but the simplest way is ABAP statement READ REPORT.
Search SO10 text element name in the source code
Get Smart Form names for the FMs from hit list. It can be done via STXFADMI table, like in below snippet, but the more correct way is SSF_FUNCTION_MODULE_NAME FM
Bingo!
Sample solution could look like this:
DATA: lt_source TYPE TABLE OF string,
lt_smartforms TYPE TABLE OF d010inc,
so_text TYPE char50,
fs_form TYPE string,
used_in TYPE TABLE OF string,
len TYPE i.
* populating the list of SmartForm FMs
SELECT * FROM d010inc AS d
INTO TABLE lt_smartforms
WHERE master LIKE '/1BCDWB/%'
AND include LIKE '/1BCDWB/%'.
so_text = '85XX_FOOTER'. " <- our SO10 text element name
LOOP AT lt_smartforms ASSIGNING FIELD-SYMBOL(<fs_fm_name>).
* reading FM source code
READ REPORT <fs_fm_name>-include INTO lt_source.
* checking if SO11 exists in source code
FIND FIRST OCCURRENCE OF so_text IN TABLE lt_source.
IF sy-subrc = 0.
len = strlen( <fs_fm_name>-include ) - 7.
* searching for SmartForm related to the target FM
SELECT SINGLE formname
FROM stxfadmi
INTO fs_form
WHERE fmnumb = <fs_fm_name>-include+len(4).
IF sy-subrc = 0.
APPEND fs_form TO used_in.
ENDIF.
ENDIF.
ENDLOOP.
Yes, it is junky, not elegant and awkward, but who said it should be so?

Concrete5 attribute counts

We built a site with Concrete5 that was originally developed in Joomla. Our job was to bring over everything and Concrete5-ize it. A major part of this site is about 1200 audio teachings, with each teaching having various attributes, such as topic, author, program, location, etc.
Some teachings might have more than one attribute assigned, say multiple keywords or topics.
I would like to give counts to all of the attributes so that the visitor can see how many teachings are by a certain author, or how many are on a particular topic at a glance, ie:
Ethics (20)
Fear (42)
Gratitude (55)
My original code turned out to have way too much overheard to be practical for so many teachings and so many attributes. Basically, I ran through and for each attribute, I did a lookup for the total count based on the PageList count. We're talking hundreds of lookups with each page load. Turning on cache didn't seem to help here.
Are there any other strategies that have proved successful for aggregating counts for attributes over a large-ish number of pages?
Here is the site for reference: http://everydayzen.org/teachings/
I typically say "don't access the database directly; use the API", but I think you should be using the DB here.
Check out the [Collection|File]SearchIndexAttributes table. (I'm not sure if teachings are files or pages. If pages, you'll need to reindex them regularly, via the job in the dashboard.) Looking at the index table will be a lot easier than joining in the most-recent version in the attribute value table. Once you see that table, you can do some simple GROUPing within SQL.
If you want to use the API, you could do it as you do today as a batch, do the appropriate calculations, and then cache it.
There's no reason caching shouldn't work but the first hit (when the cache is cold) will, of course, take the full amount of time. You should cache my IndexAttributes idea (a full table read and looping isn't trivial), but at least with a cold cache that should take a fraction of a second vs 10 or more seconds that hundreds of page list calls could take.
I've done something similar on a job site for Concrete5, by showing the counts of each Department that jobs fall in.
i.e. HR (32), Sales (12) and so on
This is the code taken from a Helper that acheives this (this is just the related functions included):
<?php
class JobHelper {
/**
* GetDepartmentJobsCount
* Returns array of Department names with job count based on input Pages
* #param Array(Pages) - Result of a PageList->getPages
* #return Array
*/
public function getDepartmentJobsCount($pages) {
$depts = $this->getDepartments();
$cj = $this->setCounts($depts);
$cj = $this->setAttributeCounts($cj, $pages,'job_department');
return $cj;
}
/**
* GetDepartments
* Return all available Departments
* #return Array(Page)
*/
public function getDepartmentPages(){
$pld = new PageList();
$pld->filterByPath('/working-lv'); //the path that your Teachings all sit under
$pld->setItemsPerPage(0);
$res = $this->getPage();
$depts = array();
foreach($res as $jp){
$depts[$jp->getCollectionName()] = $jp;
}
ksort($depts);
return $depts;
}
/**
* PopulateCounts
* Returns array of page names and counts
* #param Array - Array to feed from
* #return Array
*/
public function setCounts($v){
foreach($v as $w){
$a[$w]['count'] = 0;
}
return $a;
}
/**
* PopulateCounts
* Returns array of page names, with counts added from attribute, and paths
* #param Array - Array to add counts and paths in to
* #param Array(Pages) - Pages to run through
* #param String - Attribute to also add to counts
* #param String - Optional - Job Search parameter, leave blank to get Page URL
* #return Array
*/
public function setAttributeCounts($cj, $pages, $attr){
foreach($pages as $p) {
$pLoc = explode('|',$p->getAttribute($attr)); // Our pages could have multiple departments pipe separated
foreach($pLoc as $locName){
$cj[$locName]['count']++;
}
}
return $cj;
}
You can then do the following from a PageList template
$jh = Loader::helper('job');
$deptCounts = $jh->getDepartmentJobsCount($pages);
foreach($deptCounts as $dept => $data) {
echo $dept . '(' . $data['count] . ')';
}

Drupal 7 - How to check if a particular field has a value - sql query?

I'm trying to write a PHP code to validate a form input in a field. If the field has already value the system must send an error message. If there is not a value or the value is the same like the input then the form can be submitted.
The edited code is:
/**
* Implement a function to get the ID and the title of the referenced node
* of type Reservation
* by the nodereference field called Period
* in the currently edited node from type Board
* Try to do this by the node_load() instead of the database query
* Is it the correct method to get the edited node's ID?
**/
function period_get_value() {
$thisnodeboard = $node->field_period_1[$node->language][0]['nid'];
$reservationrec = node_load(array('nid'=>$thisnodeboard));
return $reservationrec->title;
}
/**
* Implement the hook_form_FORM_ID_alter function to validate
* if the field Period has already value set
* and if there is such to check if it is the same as the input value
**/
function period_validate_form_slickgrid_editor_form_alter(&$form, $form_state){
/**
* The current value is the title of the referenced node
**/
$valcurr = period_get_value();
$valnew = $form_state['values']['field_period_1'];
if (isset($valcurr)&&($valcurr!=$valnew)){
form_set_error('field_period_1', t('There is already value set for this field'));
}
return $form;
}
But it still doesn't work - does not set any message and allow for changing the existing value in the field_period_1.
Firstly, writing a manual SQL query in D7 is an absolute last resort.
OK so you actually want to just prevent the user from updating a field after the node has been created.
You can do one of two things. If you only want to prevent edits from the node/edit form you could implement hook_form_FORM_ID_alter() and then add your own validate or submit handler. You would then validate that the field has not changed and act accordingly.
If you wanted to prevent it happening from anywhere, Eg programmatically. You could implement hook_node_update() and check $node->is_new and $node->type to prevent changes to nodes that are not new.