Monday, January 9, 2012

Mastering the PCL4 Cluster RELID S* L* (HR Infotype change log)



The PCL4 seems complex at first sight, but its structure is really simple at the end.
Today i would like to dig into the "BELEG" (kindof  "change receipt" in german we'll call it "change log" here)
At the end of this post you will find some interesting example coding.

Table PCL4 contains many relids. We are interested in the ones produced by HR master data changes i.e. changing infotype.

Whenever you change an infotype in the "in update" in pa 30 or similar transaction  the form BELEG_SCHREIBEN  in include UP50RF20 is called.
Now "beleg schreiben" means "write a change log".


This form uses the customizing in tables T585A T585B and T585C (there is a nice view cluster to update them all at once VC_T585ABC transaction SM34)
There you tell the system

1) Which infotype to log
2) Which field of each infotypes to log
3) Whether to write the change log for each infotype to the "Long term" storage or to the "Short term storage"

Long term and short term "storage" are just two different relids
Long term -> relid LA/LB
Short term -> relid SA/SB

The A and B means the following
a) A = Master data i.e. infotypes in the PA* tables
b) B = Applicant data i.e. infotypes in the PB* tables

So if in table T585C the indication is to write to the SA/SB RELID then this happens and the KEY (SRTFD) in table PCL4 is filled with the following structure PC401
TCLAS
BDATE
BTIME
SEQNR
PERNR
INFTY

If in table T585C the indication is to write to the LA/LB RELID then this happens and the KEY (SRTFD) in table PCL4 is filled with the following structure PC400
TCLAS
PERNR
INFTY
BDATE
BTIME
SEQNR

Please note that the structure of the sort field of the PCL4 cluster table varies from relid to relid.
In this case from L* to S* the change is really significant and it can cause significant trouble when programming selects on these tables. It is really easy to write selects that bypass both primary key index and secondary indices and that simply take ages to run.

The content of the cluster are two tables
Header -> structure PC403  - contains "key information" on the changes record
SPLKZ
SUBTY
OBJPS
SPRPS
ENDDA
BEGDA
SEQNR
OPERA
AEDTM
UNAME

Belege -> structure PC404 - contains one line for each field with old and new values
SPLKZ
FIELD
FTYPE
FLENG
DECIM
OLDDT
NEWDT

So finally here is some example coding.
I tried something weird, i read the PCL4 cluster L* S*, i change the user and eventually the date of the change, and finally i write everything back to the cluster. Obviously if you have access to the database change logs you can always find out this change.
Read Carefully: Obviously this is a purely demonstrative program of how to read interpret and write change log data to PCL4 data, not a hacking tool or even worse a tool to lose your job manipulating sensitive data on your system. Be aware!
I would like to point out also that this coding is a patchwork of rpuaud00 and Infotype framework (pa30) changelog coding. So it is far from optimal or "well written".

First some word for your insight.
Reading the cluster occurs in a weird manner derived from a stripped down version of the coding used in rpuaud00. It does not support archiving and the like in my version and it is really ugly.
It saves all data read to table LCL4.
Once read it loops on this lcl4 table and for each change log it imports the data ( just like rpuaud00 ). Immediately after it deletes the PCL4 line corresponding to that change log. Then it writes the data back to the cluster, after changing the user in tables header and in the PCL4 header.
the last change is tricky because normally the RP-EXP-C4-* macros are used. The use the standard buffer and the PCL4_EXP_IMP form as an addition to the export statement. (You find these macros in tabel TRMAC). Unfortunately the export form in the buffer coding always forces the writing program and the user to sy-uname and sy-cprog. So for our needs it does not work.
So i removed the buffer form and coded the export directly, and only then i could change the PCL4 header data the way i wanted.


Here we go:

*&---------------------------------------------------------------------*
*& Report  Z_PCL4
*&
*&---------------------------------------------------------------------*
*&
*&
*&---------------------------------------------------------------------*

REPORT  Z_PCL4.

TABLES:  PCL1, PCL2.

INCLUDE RPPPXD00.
DATA: BEGIN OF COMMON PART BUFFER.
INCLUDE RPPPXD10.
DATA: END OF COMMON PART.

INCLUDE RPPPXM00.

*Datenstruktur der Belege im Cluster
INCLUDE rpcblo00.
INCLUDE rpcbsh00.
INCLUDE rpcbdt00.
rp-low-high.
rp-def-boolean.

* Makro RP_IMP_C4                                              "K015875
DEFINE rp_imp_c4.

  import_from_pcl4 &1 &2.

END-OF-DEFINITION.                                          "RP_IMP_C4

DATA: BEGIN OF LO_HEADER OCCURS 5.
        INCLUDE STRUCTURE HEADER.
DATA: END OF LO_HEADER.
DATA: BEGIN OF SH_HEADER OCCURS 5.
        INCLUDE STRUCTURE HEADER.
DATA: END OF SH_HEADER.

DATA: BEGIN OF LO_BELEGE OCCURS 100.
        INCLUDE STRUCTURE BELEGE.
DATA: END OF LO_BELEGE.
DATA: BEGIN OF SH_BELEGE OCCURS 100.
        INCLUDE STRUCTURE BELEGE.
DATA: END OF SH_BELEGE.


select-options: olduname    for  sy-uname.
parameters    : newuname    type sy-uname.
select-options: olddatum    for  sy-datum.
parameters    : newdatum    type sy-datum.

parameters: test type boole_d default 'X' as checkbox.


DATA: BEGIN OF lcl4 OCCURS 100,
        relid LIKE pcl4-relid,
        tclas LIKE lo-key-tclas,
        pernr LIKE lo-key-pernr,
        infty LIKE lo-key-infty,
        bdate LIKE lo-key-bdate,
        btime LIKE lo-key-btime,
        seqnr LIKE lo-key-seqnr,
        uname LIKE pcl4-uname,
        aedtm like pcl4-aedtm,
        srtfd like pcl4-srtfd,
        archiv LIKE admi_run-document, "QNO...
      END OF lcl4.

DATA: tabna LIKE ddftx-tabname VALUE 'Pnnnn'.               "#EC NOTEXT

DATA: BEGIN OF arch_buffer_pcl4 OCCURS 3.  "internal buffer for archive
        INCLUDE STRUCTURE pcl4.
DATA: END OF arch_buffer_pcl4.

Start-of-selection.

  PERFORM select_pcl4 USING 'LA'.      
   PERFORM select_pcl4 USING 'LB'.  
    PERFORM select_pcl4 USING 'SA'.
      PERFORM select_pcl4 USING 'SB'.

  loop at lcl4.

    perform import_data.

    loop at header.
      header-uname = newuname.
      modify header.
    endloop.
    loop at lo_header.
      lo_header-uname = newuname.
      modify lo_header.
    endloop.
    loop at sh_header.
      header-uname = newuname.
      modify sh_header.
    endloop.

    delete from pcl4 where srtfd = lcl4-srtfd.
    perform export using lcl4-relid.

*    exit.
  endloop.

*  IF test = ' '.
*    PERFORM prepare_update USING 'V'.
*  ENDIF.

  if test = 'X'.
    rollback work.
  endif.

*&---------------------------------------------------------------------*
*&      Form  import_data
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
FORM import_data.
*Simplified version (no arch and shit)
  CASE lcl4-relid.
    WHEN 'LA'.
*     IMPORT VERSION HEADER BELEGE
*            FROM DATABASE PCL4(LA) ID PCL4-SRTFD USING PCL4_EXP_IMP.
*       LO-KEY = LCL4-SRTFD.
      MOVE-CORRESPONDING lcl4 TO lo-key.
*       RP-IMP-C4-LA.
      rp_imp_c4 la lo-key.
      MOVE pcl4-srtfd TO lo-key.
      tabna+1(4) = lo-key-infty.

      lo_belege[] = belege[].
      lo_header[] = header[].


    WHEN 'LB'.
*       IMPORT VERSION HEADER BELEGE
*              FROM DATABASE PCL4(LB) ID PCL4-SRTFD.
*       LO-KEY = LCL4-SRTFD.
      MOVE-CORRESPONDING lcl4 TO lo-key.
*       RP-IMP-C4-LB.
      rp_imp_c4 lb lo-key.
      MOVE pcl4-srtfd TO lo-key.
      tabna+1(4) = lo-key-infty.
      lo_belege[] = belege[].
      lo_header[] = header[].
    WHEN 'SA'.
*     IMPORT VERSION HEADER BELEGE
*            FROM DATABASE PCL4(SH) ID PCL4-SRTFD.
*     SH-KEY = LCL4-SRTFD.
      MOVE-CORRESPONDING lcl4 TO sh-key.
*     RP-IMP-C4-SA.
      rp_imp_c4 sa sh-key.
      MOVE pcl4-srtfd TO sh-key.
      tabna+1(4) = sh-key-infty.
      sh_belege[] = belege[].
      sh_header[] = header[].
    WHEN 'SB'.
*     IMPORT VERSION HEADER BELEGE
*            FROM DATABASE PCL4(SH) ID PCL4-SRTFD.
*     SH-KEY = LCL4-SRTFD.
      MOVE-CORRESPONDING lcl4 TO sh-key.
*     RP-IMP-C4-SB.
      rp_imp_c4 sb sh-key.
      MOVE pcl4-srtfd TO sh-key.
      tabna+1(4) = sh-key-infty.
      sh_belege[] = belege[].
      sh_header[] = header[].
  ENDCASE.



ENDFORM.                               "IMPORT_DATA.


*&---------------------------------------------------------------------*
*&      Form  select_pcl4
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->VALUE(RELID)  text
*----------------------------------------------------------------------*
FORM select_pcl4 USING value(relid)     TYPE relid_pcl4.
  Data dummy.

  SELECT
   client
   relid
   srtfd
   srtf2
   histo
   aedtm
   uname
   pgmid
   versn
   clustr INTO
    (pcl4-client,
     pcl4-relid,
     pcl4-srtfd,
     pcl4-srtf2,
     pcl4-histo,
     pcl4-aedtm,
     pcl4-uname,
     pcl4-pgmid,
     pcl4-versn,
     pcl4-clustr)
       FROM pcl4  WHERE   relid EQ relid
                  AND     aedtm IN olddatum
                  AND     uname IN olduname.

    PERFORM add_pcl4 USING '000000' CHANGING dummy.
  ENDSELECT.
  .

ENDFORM.                               "SELECT_PCL4


*&---------------------------------------------------------------------*
*&      Form  add_pcl4
*&---------------------------------------------------------------------*
FORM add_pcl4 USING value(p_document) LIKE admi_run-document
              CHANGING p_added TYPE c.
  p_added = space.
  IF     pcl4-relid EQ 'LA' OR pcl4-relid EQ 'LB'.
    MOVE pcl4-srtfd TO lo-key.

    MOVE: lo-key-tclas TO lcl4-tclas,
          lo-key-pernr TO lcl4-pernr,
          lo-key-infty TO lcl4-infty,
          lo-key-bdate TO lcl4-bdate,
          lo-key-btime TO lcl4-btime,
          lo-key-seqnr TO lcl4-seqnr,
          pcl4-srtfd   to lcl4-srtfd,
          p_document TO lcl4-archiv.
    p_added = 'X'.
  ELSEIF pcl4-relid EQ 'SA' OR pcl4-relid EQ 'SB'.
    MOVE pcl4-srtfd TO sh-key.

    MOVE: sh-key-tclas TO lcl4-tclas,
          sh-key-bdate TO lcl4-bdate,
          sh-key-btime TO lcl4-btime,
          sh-key-seqnr TO lcl4-seqnr,
          sh-key-pernr TO lcl4-pernr,
          sh-key-infty TO lcl4-infty,
          pcl4-srtfd   to lcl4-srtfd.

    p_added = 'X'.
  ELSE.
    EXIT.                                                   "qno
  ENDIF.
  MOVE: pcl4-relid TO lcl4-relid,
        pcl4-aedtm to lcl4-aedtm,
        pcl4-uname TO lcl4-uname.
  APPEND lcl4.
ENDFORM.                               "SELECT_PCL4.


*export


*---------------------------------------------------------------------*
*       FORM EXPORT                                                   *
*---------------------------------------------------------------------*
*       ........                                                      *
*---------------------------------------------------------------------*
FORM EXPORT using relid.
  DATA: LINES TYPE P.

*  '--------------- E X P O R T ---------------'.+
  if relid+0(1) = 'L'.
    DESCRIBE TABLE HEADER LINES LINES.
    IF LINES GT 0.
      if not newdatum is initial.
       PCL4-AEDTM = newdatum.
      else.
       PCL4-aedtm = lcl4-aedtm.
      endif.
      PCL4-UNAME = newuname.
*      PCL4-PGMID = SY-REPID.
      move-corresponding lcl4 to lo-key.
      CASE relid.
        WHEN 'LA'.
*          RP-EXP-C4-LA.
        EXPORT VERSION
        HEADER FROM LO_HEADER
        BELEGE FROM LO_BELEGE
        TO DATABASE PCL4(LA)
        ID LO-KEY.

        WHEN 'LB'.
*          RP-EXP-C4-LB.
        EXPORT VERSION
        HEADER FROM LO_HEADER
        BELEGE FROM LO_BELEGE
        TO DATABASE PCL4(LB)
        ID LO-KEY.
      ENDCASE.
    ENDIF.
  elseif relid+0(1) = 'S'.
    DESCRIBE TABLE HEADER LINES LINES.
    IF LINES GT 0.
      if not newdatum is initial.
       PCL4-AEDTM = newdatum.
      else.
       PCL4-aedtm = lcl4-aedtm.
      endif.
      PCL4-UNAME = newuname.
*      PCL4-PGMID = SY-REPID.
      move-corresponding lcl4 to sh-key.
      CASE RELID.
        WHEN 'SA'.
*          RP-EXP-C4-SA.
          EXPORT VERSION
          HEADER FROM SH_HEADER
          BELEGE FROM SH_BELEGE
          TO DATABASE PCL4(SA)
          ID SH-KEY    .

        WHEN 'SB'.
*          RP-EXP-C4-SB.
          EXPORT VERSION
          HEADER FROM SH_HEADER
          BELEGE FROM SH_BELEGE
          TO DATABASE PCL4(SB)
          ID SH-KEY    .
      ENDCASE.
    ENDIF.
  endif.
ENDFORM. "EXPORT.