쌓고 쌓다

[ABAP] ALV Cell Edit과 BAPI_PO_CHANGE 본문

SAP/ABAP

[ABAP] ALV Cell Edit과 BAPI_PO_CHANGE

승민아 2025. 8. 28. 15:38
반응형

ALV EDIT

ALV EDIT

위의 화면처럼 ALV Cell 단위로 Edit 기능을 활성화해보자.

 

LVC_T_STYL 타입 필드 추가

Local Type

ALV에 표시할 Internal Table 데이터 타입에

셀 스타일을 위한 변수를 LVC_T_STYL 타입으로 추가한다.

 

이 필드로 ALV의 셀 스타일을 관리한다.

이 테이블을 통해 특정 필드에

HOTSPOT을 넣거나, EDIT 모드로 하거나 다양한 셀 스타일을 설정할 수 있다.

 

LAYOUT-STYLEFNAME 설정

추가한 LVC_T_STYL 타입 필드명을 써주자.

 

CELL EDIT 이벤트 등록

셀 Edit을 통해 데이터가 변경되었을 때 감지하고 이벤트를 처리하는 방법을 알아보자.

 

ALV를 초기 설정하는 단계에서 'REGISTER_EDIT_EVENT' 메소드를 호출하자.

이때 등록한 이벤트 'CL_GUI_ALV_GRID=>MC_EVT_MODIFIED'는 Enter 또는 F4를 눌렀을 때

이벤트 로직을 타도록 설정한다.

 

Enter만을 위한 CL_GUI_ALV_GRID=>MC_EVT_ENTER 값도 있다.

 

이때 발생한 이벤트 핸들러는 다음과 같이 등록한다.

 

엔터 또는 F4를 누르면 'DATA_CHANGED' 이벤트가 발생한다.

이 이벤트에서 EDIT을 통해 데이터를 변경했을 때 이후의 로직을 작성하면 된다.

 

다음은 내가 예로 만든 코드이다.

단가, 수량 금액 필드에 EDIT이 활성화되어 있는 상황이다.

이때 단가, 금액 필드에 값이 변경될 때마다 총액 NETWR 필드의 값을 계산하여 표시하는 코드이다.

 

'ER_DATA_CHANGED' 레퍼런스 변수가 가리키는 'MT_GOOD_CELLS' 속성에

데이터가 변경된 셀 정보가 들어있다.

 

ER_DATA_CHANGED 레퍼번스 변수에 GET_CELL_VALUE 메소드를 통해서

수량 'MENGE' 필드의 값과 단가 'NETPR' 필드에 변경되어 들어간 값을 읽는다.

이 값을 가지고 총액 필드 값을 경신해 주면

 

ALV EDIT을 통해 변경된 수량, 단가 필드의 값을 통해

총액 필드의 값이 계산되어 새로 화면에 뿌려줄 수 있다.

 

새로 화면에 뿌려줄 때 'REFRESH_TABLE_DISPLAY' 메소드를 사용하는데

이때 IS_STABLE 파라미터에 값을 주어 ALV의 화면이 맨 위로 올라가 버리지 않도록

스크롤을 고정할 수 있다.

 

만들어 놓은 이벤트 핸들러를 ALV에 잊지 말고 등록해 주자.

 

 

특정 CELL EDIT 열고 닫기 설정

셀 스타일에 EDIT 기능을 활성화할지 말지는

STYLE 필드에 값을 넣어주면 된다.

 

활성화 : CL_GUI_ALV_GRID=>MC_STYLE_ENABLED

비활성화 : CL_GUI_ALV_GRID=>MC_STYLE_DISABLED

 

APPEND 대신 INSERT를 사용해야 한다.

APPEND를 사용하면

 

ITAB_ILLEGAL_SORT_ORDER 덤프를 만날 수 있을 것이다.

 

LVC_T_STYL 테이블은 Sorted Table로

FIELDNAME 필드 기준으로 정렬이 되어있어야 하는데

APPEND로 순서에 맞지 않게 맨 뒤에 보내버리면 덤프가 발생하는 것이다.

 

필드 카탈로그 설정

CELL EDIT을 사용할 필드의 필드카탈로그에 EDIT 값을 'X'를 주어야

셀 EDIT이 열리고닫힌다.

 

 

ALV EDIT 추가된 Toolbar 버튼 지우기

Layout에 NO_ROWINS 행 추가를 지울 수 있고

 

+ NO_ROWINS = 'X' 기능 +

위의 행추가, 행삽입, 삭제 버튼을

지울뿐만 아니라

 

행, 셀 범위 선택후 복사(Ctrl + C)하여 셀(행)을 Ctrl + V로 붙여넣었을때

 

ALV에 행들이 추가되는것을 막을 수 있다. 

 

+ NO_ROWINS = 'X' 기능 끝 +

 

지울 버튼들을 따로 Internal Table에 담아서

 

'SET_TABLE_FOR_FIRST_DISPLAY'의 'IT_TOOLBAR_EXCLUDING' 파라미터에 넘겨주면 된다.

 

BAPI_PO_CHANGE

아래의 ALV에서 수량, 금액, 납품일을 입력하여 변경 작업을 할 수 있고

수량을 0을 입력하는 경우는 해당 품목을 삭제(지시자)하는 것이다.

 

성공 시 메시지 필드에 메시지가 나오고

실패 시 메시지 필드에 핫스팟이 생겨 에러 메시지들을 확인할 수 있도록 해보자.

 

보너스로

하단의 ALV에서 수량, 총액이 변경된다면

상단의 ALV에서 총 수량, 총 금액도 반영하도록 해보자.

 

'BAPI_PO_CHANGE' 펑션의 파라미터를 위와 같이

구매오더헤더(EKKO), 구매오더품목(EKPO), 납품일정(EKET) 정보와

변경할 필드에 'X' 표시를 하는 EKKOX, EKPOX, EKETX 정보를 넘겨준다.

 

로직에 사용되는 변수 타입들이다.

본인의 필요에 맞춰 사용하자.

 

'BAPI_PO_CHANGE' 파라미터들을 채워보자.

 

ALV 데이터를 루프문으로 돈다.

IF문은 변경을 위해 체크박스를 했고 변경 성공된 데이터가 아닌 경우만을 BAPI에 태우기 위해 조건문을 걸었다.

 

'BAPI_CURRENCY_CONV_TO_EXTERNAL' 펑션은 왜 썼느냐?

'BAPI_PO_CHANGE'에 넘겨주는 품목 단가 NET_PRICE에 화폐 단위를 적용한 금액을 넘겨주기 위해 사용했다.

 

왜 화폐 단위를 적용한 금액을 넘겨주어야 하느냐?

 

우리가 ALV를 통해 보는 KRW 금액은 100이 곱해져 있다.

우리가 현재 ALV로 보는 데이터에 100 KRW라면 실제 필드에는 0.01 KRW가 저장되어 있는 것이다.

BAPI에 태울 때 0.01 KRW를 그대로 넘기면

BAPI는 KRW라고 또 100을 나눠버린 0.0001 값을 DB에 저장하게 되는 것이다.

 

즉, 우리는 DB에 0.01 KRW를 저장하길 원하지만

BAPI에서 또 100을 나눈 값을 저장해 버리기 때문에

'BAPI_CURRENCY_CONV_TO_EXTERNAL' 펑션을 통해 KRW라면 100을 곱해주는 과정을 해주는 것이다.

 

 

(추가적으로 알았는데 구매오더 EKKO에 삭제지시자에 값을 주면 

구매오더 품목들 모두에 삭제지시자가 부여되더라~)

 

 ALV EDIT을 통해 수량을 0으로 바꾼 경우에는

삭제 지시자 필드인 DELETE_IND에 값을 주어 삭제 지시자 필드인 LOEKZ에 값을 줄 수 있다.

 

'BAPI_PO_CHANGE' 호출하면 끝이다...

이제 호출 결과를 담아주는 RETURN 파라미터를 통해 에러 메시지를 처리해 보자.

 

BAPI 에러 메시지

 에러 상황시 원래의 값으로 되돌리기 위해 수량, 단가, 총액 필드를 하나 더 추가했다.

앞에 S가 붙는 필드는 성공 시(실제 DB에 반영된) 갱신되는 필드이다.

 

호출 결과가 RETURN 파라미터를 통해 넘어오는데 BAPIRET2 타입이다.

이 값을 BAPIMSG 필드에 저장해 줄 것이다.

 

이제부터의 코드는 'BAPI_PO_CHANGE' 호출 이후의 코드들이다.

 

RETURN으로 받은 결과 테이블을 우선 BAPIMSG 필드에 할당한다.

그리고 반복문을 돌며 에러 메시지가 존재하는지 확인한다.

 

바로 'S' 타입이나 'E' 타입으로 성공유무를 판단하면 되지 않느냐? 할 수 있다.

위의 로직은 아래의 상황 때문에 추가되었다.

 

수량과 단가를 변경했을 때

수량만 변경되고 단가는 변경되지 않았을 때

'S' 타입의 메시지가 나오지만

'I' 타입의 '664'번 메시지로 단가 변경 무효화 메시지가 나올 수 있기 때문에 

위처럼 코드를 작성했다...

 

다른 블로그들을 보면 

TYPE = 'S' NUMBER = '023'.로 성공 유무를 판단하는데

그 이유는

성공인 'S' 타입이더라도

데이터 변경 내용이 없을 경우 '022'번 메시지가 나온다.

즉 확실히 변경 사항이 있고 변경되었을 때 '023'번 메시지가 나오기에

'S'와 '023'을 함께 묶어 성공 판단을 하는 것이다.

 

다시  LOOP문으로 돌아가서..

LOOP문에 한 번이라도 걸리는 경우에는 실패의 경우로 치며

LV_UPDATE_FAIL 변수에 참을 할당한다.

 

업데이트 실패를 하지 않고

정상적인 업데이트가 되었을 때 조건문이다.

 

'BAPI_TRANSACTION_COMMIT'을 통해 커밋을 하며

WAIT는 커밋이 완료될 때까지 기다리는 것. 동기방식이다.

 

커밋이 완료되었다면 품목의 수량과 단가, 총액을 갱신하고 계산한다.

이때 CELLSTYL 테이블을 건드려 해당 ROW의 CELL EDIT을 비활성화할 수 있다.

 

다음은 업데이트 실패 시 코드이다.

다시 화면의 필드 값을 입력하기 전 값인 S가 붙은 필드들로 갱신하고

'BAPI_TRANSACTION_ROLLBACK'을 통해 롤백한다.

 

READ TABLE 구문은 해당 ROW의 CELLSTYL 필드에

'MESSAGE' 필드명을 갖는 셀 스타일 데이터가 있는지 확인하고

있다면 해당 셀을 HOTSPOT 스타일로 바꾼다.

없다면 CELLSTYL을 INSERT 하여 'MESSAGE' 필드를 핫스팟 스타일로 바꾼다.

 

핫스팟 클릭 이벤트와 'RSCRMBW_DISPLAY_BAPIRET2' 펑션을 이용하면

 

위처럼 펑션을 통해 바로 ALV에 오류 메시지를 출력할 수 있다.

 

성공 실패 상황을 모두 처리한 후 LOOP문 내부의 끝에서

ALV 헤더에 표시된 총 수량, 금액을 반영하기 위해

총 수량, 금액을 갱신해 준다.

 

그러면 품목의 데이터가 변경될 때마다

헤더의 총 수량, 금액도 맞춰 화면에 보여줄 수 있다.

 

헤더의 총 금액(NETWR), 총 수량(MENGE)를 갱신하는 코드이다.

헤더 ALV에 뿌려진 Internal Table에서 선택된 ROW의 헤더의 총 금액, 수량을 갱신하는 부분이다.

 

전체 코드

더보기

FORM PO_UPDATE .

  DATA LT_RETURN LIKE TABLE OF BAPIRET2" BAPI 호출 결과
         LS_RETURN LIKE LINE OF LT_RETURN,
         LT_EKPO   LIKE TABLE OF BAPIMEPOITEM" PO 아이템 필드 값 변경할 때 사용
         LS_EKPO   LIKE LINE OF LT_EKPO,
         LT_EKPOX  LIKE TABLE OF BAPIMEPOITEMX" PO 아이템 필드에 대해 변경 여부 X 표시
         LS_EKPOX  LIKE LINE OF LT_EKPOX,
         LT_EKET   LIKE TABLE OF BAPIMEPOSCHEDULE,
         LS_EKET   LIKE LINE OF LT_EKET,
         LT_EKETX  LIKE TABLE OF BAPIMEPOSCHEDULX,
         LS_EKETX  LIKE LINE OF LT_EKETX.

  DATA LS_EKKO  TYPE BAPIMEPOHEADER,
         LS_EKKOX TYPE BAPIMEPOHEADERX.

  DATA LV_SELECT_FLAG TYPE ABAP_BOOL" PO 품목 선택 유무 플래그
         LV_UPDATE_FAIL TYPE ABAP_BOOL" PO 업데이트 실패 유무

  DATA LV_SNETWR_SUM TYPE TY_PO_HEADER-NETWR,
         LV_SMENGE_SUM TYPE TY_PO_HEADER-MENGE.

  DATA LS_CELLSTYL TYPE LVC_S_STYL" 메시지 셀 스타일 추가를 위한 변수
  FIELD-SYMBOLS <FS_CELLSTYL> TYPE LVC_S_STYL.

  LOOP AT GT_PO_ITEM ASSIGNING FIELD-SYMBOL(<FS_PO_ITEM>).

    IF <FS_PO_ITEM>-CHKBOX 'X' AND <FS_PO_ITEM>-STATUS <> 3" 3 : 성공

      DATA LV_AFTER_NETPR  TYPE BAPICURR_D.
      CALL FUNCTION 'BAPI_CURRENCY_CONV_TO_EXTERNAL'
        EXPORTING
          AMOUNT_INTERNAL <FS_PO_ITEM>-NETPR
          CURRENCY        <FS_PO_ITEM>-WAERS
        IMPORTING
          AMOUNT_EXTERNAL LV_AFTER_NETPR.

      LS_EKKO-PO_NUMBER <FS_PO_ITEM>-EBELN.
      LS_EKKOX-PO_NUMBER 'X'.
*      LS_EKKO-DELETE_IND = ABAP_TRUE. " 헤더 삭제 지시자 -> 만약 헤더가 삭제되면 모든 품목도 삭제되더라.
*      LS_EKKOX-DELETE_IND = 'X'.

*     변경 대상 PO 품목 세팅
      LS_EKPO-PO_ITEM <FS_PO_ITEM>-EBELP.
      LS_EKPOX-PO_ITEM <FS_PO_ITEM>-EBELP.
      LS_EKPOX-PO_ITEMX ABAP_TRUE.
      LS_EKET-PO_ITEM <FS_PO_ITEM>-EBELP.
      LS_EKETX-PO_ITEMX ABAP_TRUE.

*     가격 설정
      LS_EKPO-NET_PRICE LV_AFTER_NETPR.
      LS_EKPOX-NET_PRICE ABAP_TRUE.

*     수량 0이라면 삭제
      IF <FS_PO_ITEM>-MENGE 0.
        LS_EKPO-DELETE_IND ABAP_TRUE.
        LS_EKPOX-DELETE_IND ABAP_TRUE.
      ELSE" 0이 아니라면 수량 설정
        LS_EKPO-QUANTITY <FS_PO_ITEM>-MENGE.
        LS_EKPOX-QUANTITY ABAP_TRUE.
      ENDIF.

      LS_EKET-PO_ITEM <FS_PO_ITEM>-EBELP.
      LS_EKETX-PO_ITEM <FS_PO_ITEM>-EBELP.
      LS_EKET-DELIVERY_DATE <FS_PO_ITEM>-EINDT.
      LS_EKETX-DELIVERY_DATE ABAP_TRUE.

      APPEND LS_EKPO TO LT_EKPO.
      APPEND LS_EKPOX TO LT_EKPOX.
      APPEND LS_EKET TO LT_EKET.
      APPEND LS_EKETX TO LT_EKETX.

      CALL FUNCTION 'BAPI_PO_CHANGE'
        EXPORTING
          PURCHASEORDER <FS_PO_ITEM>-EBELN " 구매오더번호
          POHEADER      LS_EKKO
          POHEADERX     LS_EKKOX
        TABLES
          RETURN        LT_RETURN " 결과 메시지
          POITEM        LT_EKPO " 품목
          POITEMX       LT_EKPOX " 변경할 품목 필드
          POSCHEDULE    LT_EKET " 납품일정
          POSCHEDULEX   LT_EKETX" 변경할 납품일정 필드

*--------------------------------------------------------------------*
*     1. I 타입 664번 메시지 고려 버전


      <FS_PO_ITEM>-BAPIMSG LT_RETURN.
      LOOP AT <FS_PO_ITEM>-BAPIMSG ASSIGNING FIELD-SYMBOL(<FS_RETURN>)
        WHERE TYPE 'I' AND NUMBER '664' OR TYPE 'E'.
        LV_UPDATE_FAIL ABAP_TRUE.
        <FS_RETURN>-TYPE 'E'.
        <FS_PO_ITEM>-STATUS 1" 빨간불
        <FS_PO_ITEM>-MESSAGE <FS_RETURN>-MESSAGE" 기본 메시지 한 개 ALV에 출력
      ENDLOOP.

      IF LV_UPDATE_FAIL ABAP_FALSE.

        CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
          EXPORTING
            WAIT 'X'.
        READ TABLE LT_RETURN INTO LS_RETURN WITH KEY TYPE 'S'.
        <FS_PO_ITEM>-STATUS 3" 초록불
        <FS_PO_ITEM>-MESSAGE LS_RETURN-MESSAGE.

        <FS_PO_ITEM>-SMENGE <FS_PO_ITEM>-MENGE" 새로운 수량 계산
        <FS_PO_ITEM>-SEINDT <FS_PO_ITEM>-EINDT" 납품일
        <FS_PO_ITEM>-SNETPR <FS_PO_ITEM>-NETPR" 금액
        <FS_PO_ITEM>-NETWR  <FS_PO_ITEM>-NETPR * <FS_PO_ITEM>-MENGE" 새로운 총액 계산
        <FS_PO_ITEM>-SNETWR <FS_PO_ITEM>-NETWR" 실제 총액 업데이트

        " 성공 시 EDIT 잠그기
        LOOP AT <FS_PO_ITEM>-CELLSTYL ASSIGNING <FS_CELLSTYL>.
          <FS_CELLSTYL>-STYLE = CL_GUI_ALV_GRID=>MC_STYLE_DISABLED.
        ENDLOOP.
      ELSE.
        <FS_PO_ITEM>-NETPR <FS_PO_ITEM>-SNETPR.
        <FS_PO_ITEM>-MENGE <FS_PO_ITEM>-SMENGE.
        <FS_PO_ITEM>-NETWR <FS_PO_ITEM>-NETPR * <FS_PO_ITEM>-MENGE.

        CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.

        READ TABLE <FS_PO_ITEM>-CELLSTYL ASSIGNING <FS_CELLSTYL> WITH KEY FIELDNAME 'MESSAGE'.
        IF SY-SUBRC 0" MESSAGE 필드에 스타일 데이터가 존재하면
          <FS_CELLSTYL>-STYLE = CL_GUI_ALV_GRID=>MC_STYLE_HOTSPOT.
        ELSE" 미존재하면
          LS_CELLSTYL-FIELDNAME 'MESSAGE'.
          LS_CELLSTYL-STYLE = CL_GUI_ALV_GRID=>MC_STYLE_HOTSPOT.
          INSERT LS_CELLSTYL INTO TABLE <FS_PO_ITEM>-CELLSTYL.
        ENDIF.
      ENDIF.
      LV_SELECT_FLAG ABAP_TRUE.
    ENDIF.

    LV_SNETWR_SUM LV_SNETWR_SUM + <FS_PO_ITEM>-SNETWR" 총 금액 변수 더하기
    LV_SMENGE_SUM LV_SMENGE_SUM + <FS_PO_ITEM>-SMENGE" 총 수량 더하기

  ENDLOOP.

  IF LV_SELECT_FLAG ABAP_FALSE.
    MESSAGE '변경할 PO를 선택해 주세요.' TYPE 'S' DISPLAY LIKE 'E'.
    RETURN.
  ENDIF.

  GS_PO_HEADER-NETWR LV_SNETWR_SUM.
  GS_PO_HEADER-MENGE LV_SMENGE_SUM.
  MODIFY TABLE GT_PO_HEADER FROM GS_PO_HEADER TRANSPORTING NETWR MENGE.


ENDFORM.

 

반응형