需求描述:

销售开票时检查行项目净值是否为0,如果为0则报错不允许开票,涉及Tcode VF01和VF04。

实现过程:

销售开票VF01和VF04底层都是调用的函数RV_INVOICE_CREATE。

此需求需要考虑前台操作和自开发程序,所以应该在开票保存时增加检查。

销售开票存在增强点BADI_SD_BILLING,当点击开票时候会调用方法IF_EX_BADI_SD_BILLING~INVOICE_DOCUMENT_CHECK,只是该增强点为SAP内部使用。

VF01 Vf01增强开票_sap

好,既然此增强不能为我所用,那就在某个看的比较顺眼的已经激活的实施中加入隐式增强(注意一定要选已经激活的!活的!),恭喜实施SIPT_SD_BILLING中奖。

VF01 Vf01增强开票_abap_02

在调用方法IF_EX_BADI_SD_BILLING~INVOICE_DOCUMENT_CHECK结束处增加隐式增强。

此处有两个注意点:

1. 开票错误需能够在日志中显示,日志其实是保存在全局变量(SAPLV60A)XVBFS[]中,参照SAP标准程序可以调用函数SIPT_VBFS_CREATE_MESSAGE_SD写入错误日志。

2. VF01或者VF04前台执行时,出现错误可以直接抛出E类型消息。而VF04可以定义成后台作业,当后台批量开票时如果单张发票报错不应该影响后面其他正常发票生成,所以不能抛出E类型消息,但该方法有个传出参数FBAD_DATA设为"X"则会终止此发票生成。

method IF_EX_BADI_SD_BILLING~INVOICE_DOCUMENT_CHECK.

  CALL FUNCTION 'SIPT_DOC_CHECK_SD'
    EXPORTING
      it_xvbrk    = fxvbrk
      it_xvbrp    = fxvbrp
      it_xkomv    = fxkomv
      it_xvbpa    = fxvbpa
    IMPORTING
      ev_bad_data = fbad_data.

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""$"$\SE:(1) Class SIPT_SD_BILLING, Interface IF_EX_BADI_SD_BILLING, Method INVOICE_DOCUMENT_CHECK, End                                                        A
*$*$-Start: (1)---------------------------------------------------------------------------------$*$*
ENHANCEMENT 1  ZIMPL_BADI_SD_BILLING.    "active version
  DATA: LT_ZTSD019 TYPE STANDARD TABLE OF ZTSD019,
        LS_XVBRK   TYPE LINE OF VBRKVB_T,
        LS_XVBRP   TYPE LINE OF VBRPVB_T,
        LV_DUMMY   TYPE C.

  CHECK SY-TCODE = 'VF01' OR SY-TCODE = 'VF04' OR SY-CPROG = 'SDBILLDL'.

  IF FXVBRK[] IS NOT INITIAL.
*   查询排除检查配置表
    SELECT *
      INTO TABLE LT_ZTSD019
      FROM ZTSD019
       FOR ALL ENTRIES IN FXVBRK
     WHERE FKART = FXVBRK-FKART.
    SORT LT_ZTSD019 BY FKART PSTYV.
  ENDIF.

* 检查发票净值是否为0
  LOOP AT FXVBRK INTO LS_XVBRK.
    LOOP AT FXVBRP INTO LS_XVBRP WHERE VBELN = LS_XVBRK-VBELN.
      READ TABLE LT_ZTSD019 TRANSPORTING NO FIELDS
            WITH KEY FKART = LS_XVBRK-FKART
                     PSTYV = LS_XVBRP-PSTYV BINARY SEARCH.
      IF SY-SUBRC = 0.
        CONTINUE.
      ENDIF.

      IF LS_XVBRP-FKIMG IS NOT INITIAL AND LS_XVBRP-NETWR IS INITIAL.
        MESSAGE E007(ZSD01) INTO LV_DUMMY.
        CALL FUNCTION 'SIPT_VBFS_CREATE_MESSAGE_SD'
            EXPORTING
              IV_VBELN = LS_XVBRK-VBELN
              IV_POSNR = '000000'
              IV_MSGID = SY-MSGID
              IV_MSGTY = SY-MSGTY
              IV_MSGNO = SY-MSGNO
              IV_MSGV1 = SY-MSGV1
              IV_MSGV2 = SY-MSGV2
              IV_MSGV3 = SY-MSGV3
              IV_MSGV4 = SY-MSGV4.

        IF SY-BATCH IS INITIAL.
          MESSAGE E007(ZSD01).
        ELSE.
          FBAD_DATA = 'X'.
        ENDIF.
      ENDIF.
    ENDLOOP.
  ENDLOOP.
ENDENHANCEMENT.
*$*$-End:   (1)---------------------------------------------------------------------------------$*$*
ENDMETHOD.

这样这个功能就算实现完成,但是存在一个缺陷,即VF01开票时候会先进入开票明细界面,然后用户点击保存时候才会触发校验,而用户希望能在输入交货单回车后就校验,效果如下图所示

VF01 Vf01增强开票_VF01_03

查看日志能够看到错误消息

VF01 Vf01增强开票_全局变量_04

这个需求实现略微波折,通过Debug发现价格计算逻辑比较靠后,是在函数RV_INVOICE_CREATE调用Form DRAFT_LOESCHEN_VORBEREITEN之后才得出,堆栈如下

VF01 Vf01增强开票_VF01_05

 然后寻找是否存在二代和三代增强,无果,只能采用隐式增强,下面就是位置选择,通过查看程序发现上层Form调用完该Form后就退出了,所以增强不能写在上层Form中

VF01 Vf01增强开票_abap_06

 再上层是函数RV_INVOICE_CREATE,发现调用完该Form之后更新标识被打上了“X",这是我们不希望的结果,所以增强点只能写在调用Form结束处

VF01 Vf01增强开票_sap_07

 确定增强位置之后,接下来是解决如何抛出错误消息以及终止开票。

报错消息依然保存在全局变量(SAPLV60A)XVBFS[]中,此外还需将消息号写入变量(SAPLV60A)XKOMFK-FXMSG。

终止开票没有单独变量可以控制,并且此时开票数据已经写入了全局变量,所以只能删除全局变量里的数据来终止开票,需要修改的全局变量为程序SAPLV60A里的XVBRK[]、XVBRP[]、XVBPA[]、XKOMV[]、XVBFA[]、XVBUK[]、XVBUP[]。

这边存在一个注意点,VF01开票和VF04单个凭证开票都是一个交货单生成一张发票,VF04集中开票凭证如果多个交货单开票抬头信息一致则多个交货单会合并生成一张发票。我这边接到的需求是一个交货单的行项目存在报错则该交货单的所有行项目都不开票,并且不影响集中开票里其他正常交货单开票。所以需要按照交货单号删除全局变量里的数据,同时需要考虑临时发票号变量(SAPLV60A)BELEG。

具体代码如下

*----------------------------------------------------------------------*
***INCLUDE LV60AD60.
*----------------------------------------------------------------------*
*&---------------------------------------------------------------------*
*&      Form  DRAFT_LOESCHEN_VORBEREITEN
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*  -->  p1        text
*  <--  p2        text
*----------------------------------------------------------------------*
FORM draft_loeschen_vorbereiten .
*  data: lt_snapshot type if_sd_bil_type_def=>tt_draft_document.
*  data: ls_snapshot type if_sd_bil_type_def=>ty_draft_document.
*  data: ls_xvbfa type vbfavb.
*  data: lt_temp_xvbfa type table of vbfavb.
*
*  loop at xvbfa into ls_xvbfa.
*    if CL_SD_DOC_CATEGORY_UTIL=>IS_ANY_INVOICE( ls_xvbfa-vbtyp_n ) = ABAP_TRUE.
*      read table lt_snapshot with key vbeln = ls_xvbfa-vbeln transporting no fields binary search.
*      CASE sy-subrc.
*        WHEN 4.
*          ls_snapshot-vbeln = ls_xvbfa-vbeln.
*          INSERT ls_snapshot INTO lt_snapshot INDEX sy-tabix.
*        WHEN 8.
*          ls_snapshot-vbeln = ls_xvbfa-vbeln.
*          APPEND ls_snapshot TO lt_snapshot.
*      endcase.
*    endif.
*  endloop.
*  if not lt_snapshot[] is initial.
*    lt_temP_xvbfa[] = xvbfa[].
*    CALL FUNCTION 'RV_DRAFT_DELETE_PREPARE'
*      EXPORTING
*        it_draft           = lt_snapshot
*     IV_SNAPSHOTS_ONLY       = ABAP_FALSE
*      importing
*        et_xvbrk_del       = xvbrkd
*        et_xvbrp_del       = xvbrpd
*        et_xvbpa_del       = xvbpad
*        et_xvbfa_del       = xvbfad.
*    xvbfa[] = lt_temp_xvbfa[].
*  endif.
***
  DATA : ls_xvbfa   TYPE vbfavb.
  DATA : ls_vbrk    TYPE vbrk.
  DATA : ls_vbrk_wa TYPE vbrkvb.
  DATA : ls_vbrp_wa TYPE vbrpvb.
  DATA : ls_vbpa_wa TYPE vbpavb.
  DATA : ls_vbfa_wa TYPE vbfavb.
  DATA : ls_komv_wa TYPE komv.
  DATA : temp_xvbfa  TYPE TABLE OF vbfavb.
  DATA : temp_xvbrk  TYPE TABLE OF vbrkvb.
  DATA : temp_xvbrp  TYPE TABLE OF vbrpvb.
  DATA : temp_xvbpa  TYPE TABLE OF vbpavb.
  DATA : temp_xkomv  TYPE TABLE OF komv.
  DATA : ls_xvbrkd   TYPE vbrkvb.

  DATA: vbfa_condensed TYPE HASHED TABLE OF vbeln WITH UNIQUE KEY table_line.
  DATA: lt_xvbrk_draft_check TYPE HASHED TABLE OF vbeln WITH UNIQUE KEY table_line.
  DATA: lt_xvbrk_sorted LIKE SORTED TABLE OF xvbrk WITH NON-UNIQUE KEY vbeln.


  CHECK: gv_draft_mode IS INITIAL.
  lt_xvbrk_sorted = xvbrk[]. "implicit sorting
*  refresh xvbrkd.
*  refresh xvbrpd.
*  refresh xvbpad.
**  REFRESH xkomvd.
*  refresh xvbfad.
  LOOP AT xvbfa INTO ls_xvbfa.
    IF ls_xvbfa-vbeln(1) <> '$' AND cl_sd_doc_category_util=>is_any_invoice( ls_xvbfa-vbtyp_n ) = abap_true.
      READ TABLE lt_xvbrk_sorted WITH TABLE KEY vbeln = ls_xvbfa-vbeln ASSIGNING FIELD-SYMBOL(<ls_xvbrk>).
      IF sy-subrc = 0.
        IF <ls_xvbrk>-draft IS NOT INITIAL. "it is a draft document; found in xvbrk
          INSERT ls_xvbfa-vbeln INTO TABLE vbfa_condensed.
        ENDIF.
      ELSE. "document not in xvbrk table - need to check draft status from DB
        INSERT ls_xvbfa-vbeln INTO TABLE lt_xvbrk_draft_check.
      ENDIF.
    ENDIF.
  ENDLOOP.
  IF lt_xvbrk_draft_check IS NOT INITIAL. "Get draft status from DB
    SELECT vbeln FROM vbrk INTO TABLE @lt_xvbrk_draft_check FOR ALL ENTRIES IN @lt_xvbrk_draft_check WHERE vbeln = @lt_xvbrk_draft_check-table_line AND draft = 'X'.
    INSERT LINES OF lt_xvbrk_draft_check INTO TABLE vbfa_condensed.
  ENDIF.
  LOOP AT vbfa_condensed ASSIGNING FIELD-SYMBOL(<ls_vbfa>).
    REFRESH temp_xvbrk.
    REFRESH temp_xvbrp.
    REFRESH temp_xvbpa.
*    refresh temp_xkomv.
    REFRESH temp_xvbfa.
*    if CL_SD_DOC_CATEGORY_UTIL=>IS_ANY_INVOICE( ls_xvbfa-vbtyp_n ) = ABAP_TRUE.
*    IF NOT ls_vbfa_condensed-vbeln(1) EQ '$'.
    CLEAR ls_vbrk.
    ls_vbrk-vbeln = <ls_vbfa>.
*     FM RV_INVOICE_DOCUMENT_READ sets t180-trtyp to 'A' which causes some issues in pricing later on (item net value is zero)
*     Workaround: Save t180-trtyp before FM call and it after FM call
    DATA(lv_trtyp) = t180-trtyp.
    CALL FUNCTION 'RV_INVOICE_DOCUMENT_READ'
      EXPORTING
        activity             = '03'
*       konv_read            = 'X'
        vbrk_i               = ls_vbrk
        i_no_authority_check = 'X'
*          IMPORTING
*       VBRK_E               = *VBRK
*       VBUK_E               = *VBUK
      TABLES
        xvbrk                = temp_xvbrk
        xvbrp                = temp_xvbrp
        xvbpa                = temp_xvbpa
        xkomv                = temp_xkomv
      EXCEPTIONS
        error_message        = 01.
    t180-trtyp = lv_trtyp.
    IF sy-subrc IS INITIAL.
      LOOP AT temp_xvbrk INTO ls_vbrk.
        IF NOT ls_vbrk-draft IS INITIAL.
*            ls_vbfa_condensed-draft = ls_vbrk-draft.
*            MODIFY vbfa_condensed FROM ls_vbfa_condensed INDEX lv_condensed_tabix.
          ls_vbrk_wa-updkz = 'D'.
          ls_vbrp_wa-updkz = 'D'.
          ls_vbpa_wa-updkz = 'D'.
          ls_vbfa_wa-updkz = 'D'.
*            ls_komv_wa-updkz = 'D'.
          MODIFY temp_xvbrk FROM ls_vbrk_wa TRANSPORTING updkz WHERE vbeln = ls_vbrk-vbeln.
          MODIFY temp_xvbrp FROM ls_vbrp_wa TRANSPORTING updkz WHERE vbeln = ls_vbrk-vbeln.
          MODIFY temp_xvbpa FROM ls_vbpa_wa TRANSPORTING updkz WHERE vbeln = ls_vbrk-vbeln.
          MODIFY temp_xvbfa FROM ls_vbfa_wa TRANSPORTING updkz WHERE vbeln = ls_vbrk-vbeln.
*            IF NOT temp_komv[] IS INITIAL.
*              MODIFY temp_xkomv FROM ls_komv_wa TRANSPORTING updkz WHERE knumv = ls_vbrk-knumv.
*            ELSE.
*              READ TABLE xkomv WITH KEY mandt = ls_vbrk-mandt
*                                        knumv = ls_vbrk-knumv
*                                        TRANSPORTING NO FIELDS BINARY SEARCH.
*              LOOP AT xkomv INTO ls_komv_wa FROM sy-tabix.
*                IF NOT xkomv-knumv = ls_vbrk-knumv.
*                  EXIT.
*                ELSE.
*                  READ TABLE temp_xkomv WITH KEY mandt = xkomv-mandt
*                                                                 knumv = xkomv-knumv
*                                                                 kposn = xkomv-kposn
*                                                                 TRANSPORTING NO FIELDS BINARY SEARCH.
*                  CASE sy-subrc.
*                    WHEN 4.
*                      ls_komv_wa-updkz = 'D'.
*                      INSERT ls_komv_wa INTO xkomvd INDEX sy-tabix.
*                    WHEN 8.
*                      ls_komv_wa-updkz = 'D'.
*                      APPEND ls_komv_wa TO xkomvd.
*                  ENDCASE.
*                ENDIF.
*              ENDLOOP.
*            ENDIF.
*             INSERT TEMP_XVBRK into XVBRK
          READ TABLE temp_xvbrk INTO ls_vbrk_wa INDEX 1.
          IF sy-subrc IS INITIAL.
            READ TABLE xvbrkd WITH KEY vbeln = ls_vbrk_wa-vbeln TRANSPORTING NO FIELDS BINARY SEARCH.
            CASE sy-subrc.
              WHEN 4.
                INSERT ls_vbrk_wa INTO xvbrkd INDEX sy-tabix.
              WHEN 8.
                APPEND ls_vbrk_wa TO xvbrkd.
            ENDCASE.
          ENDIF.
*             Insert TEMP_XVBRP into XVBRP
          READ TABLE temp_xvbrp INTO ls_vbrp_wa INDEX 1.
          IF sy-subrc IS INITIAL.
            READ TABLE xvbrpd WITH KEY vbeln = ls_vbrp_wa-vbeln
                                       posnr = ls_vbrp_wa-posnr
                                      TRANSPORTING NO FIELDS BINARY SEARCH.
            CASE sy-subrc.
              WHEN 4.
                INSERT LINES OF temp_xvbrp INTO xvbrpd INDEX sy-tabix.
              WHEN 8.
                APPEND LINES OF temp_xvbrp TO xvbrpd.
            ENDCASE.

          ENDIF.
*             Insert TEMP_XVBPA into XVBPA
          READ TABLE temp_xvbpa INTO ls_vbpa_wa INDEX 1.
          IF sy-subrc IS INITIAL.
            READ TABLE xvbpad WITH KEY vbeln = ls_vbpa_wa-vbeln
                                      posnr = ls_vbpa_wa-posnr
                                      TRANSPORTING NO FIELDS BINARY SEARCH.
            CASE sy-subrc.
              WHEN 4.
                INSERT LINES OF temp_xvbpa INTO xvbpad INDEX sy-tabix.
              WHEN 8.
                APPEND LINES OF temp_xvbpa TO xvbpad.
            ENDCASE.

          ENDIF.
**             Insert TEMP_XKOMV into XKOMV
*            READ TABLE temp_xkomv into ls_komv_wa INDEX 1.
*            IF sy-subrc IS INITIAL.
*              READ TABLE xkomvd WITH KEY mandt = ls_komv_wa-mandt
*                                        knumv = ls_komv_wa-knumv
*                                        kposn = ls_komv_wa-kposn
*                                        TRANSPORTING NO FIELDS BINARY SEARCH.
*              CASE sy-subrc.
*                WHEN 4.
*                  INSERT LINES OF temp_xkomv INTO xkomvd INDEX sy-tabix.
*                WHEN 8.
*                  APPEND LINES OF temp_xkomv TO xkomvd.
*              ENDCASE.
*
*            ENDIF.

        ENDIF.
      ENDLOOP.
    ENDIF.
*    ENDIF.
*    endif.
  ENDLOOP.
  LOOP AT xvbfa INTO ls_xvbfa.
*    read table vbfa_condensed with key vbeln = ls_xvbfa-vbelv into ls_vbfa_condensed BINARY SEARCH.
*    if sy-subrc is initial.
*      if not ls_vbfa_condensed-draft is initial.
*        read table xvbfad with key vbelv = ls_xvbfa-vbelv
*                                   posnv = ls_xvbfa-posnv
*                                   vbeln = ls_xvbfa-vbeln
*                                   posnn = ls_xvbfa-posnn
*                                   TRANSPORTING NO FIELDS BINARY SEARCH.
*        CASE sy-subrc.
*          WHEN 4.
*            INSERT ls_xvbfa INTO xvbfad INDEX sy-tabix.
*          WHEN 8.
*            APPEND ls_xvbfa TO xvbfad.
*        ENDCASE.
*      endif.
*    endif.
    READ TABLE vbfa_condensed WITH TABLE KEY table_line = ls_xvbfa-vbeln TRANSPORTING NO FIELDS.
    IF sy-subrc IS INITIAL.

      READ TABLE xvbfad WITH KEY vbelv = ls_xvbfa-vbelv
                                 posnv = ls_xvbfa-posnv
                                 vbeln = ls_xvbfa-vbeln
                                 posnn = ls_xvbfa-posnn
                                 TRANSPORTING NO FIELDS BINARY SEARCH.
      CASE sy-subrc.
        WHEN 4.
          INSERT ls_xvbfa INTO xvbfad INDEX sy-tabix.
        WHEN 8.
          APPEND ls_xvbfa TO xvbfad.
      ENDCASE.

    ENDIF.
  ENDLOOP.
  SORT xvbuk BY vbeln.
  LOOP AT xvbrkd INTO ls_xvbrkd.
    READ TABLE xvbuk WITH KEY vbeln = ls_xvbrkd-vbeln TRANSPORTING NO FIELDS BINARY SEARCH.
    IF sy-subrc IS INITIAL.
      DELETE xvbuk INDEX sy-tabix.
    ENDIF.
    DELETE xvbfa WHERE vbelv = ls_xvbrkd-vbeln.
    DELETE xvbfa WHERE vbeln = ls_xvbrkd-vbeln.

* delete Output Items that were created for Snapshots:
    IF ls_xvbrkd-isnastactive EQ abap_false. "Output shall be performed via NAST
      PERFORM delete_output USING ls_xvbrkd-vbeln.
    ENDIF.

  ENDLOOP.
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""$"$\SE:(1) Form DRAFT_LOESCHEN_VORBEREITEN, End                                                                                                              A
*$*$-Start: (1)---------------------------------------------------------------------------------$*$*
ENHANCEMENT 1  ZEHM_QTC077_001.    "active version
  DATA: LV_ERROR TYPE CHAR1.

  IF XVBRK[] IS NOT INITIAL.
*   查询排除检查配置表
    SELECT FKART,
           PSTYV
      INTO TABLE @DATA(LT_ZTSD019)
      FROM ZTSD019
       FOR ALL ENTRIES IN @XVBRK
     WHERE FKART = @XVBRK-FKART.
    SORT LT_ZTSD019 BY FKART PSTYV.
  ENDIF.

* 检查发票净值是否为0
  LOOP AT XVBRK WHERE VBELN = BELEG.
    CLEAR LV_ERROR.

    LOOP AT XVBRP WHERE VBELN = XVBRK-VBELN.
      READ TABLE LT_ZTSD019 TRANSPORTING NO FIELDS
            WITH KEY FKART = XVBRK-FKART
                     PSTYV = XVBRP-PSTYV BINARY SEARCH.
      IF SY-SUBRC = 0.
        CONTINUE.
      ENDIF.

      IF XVBRP-FKIMG IS NOT INITIAL AND XVBRP-NETWR IS INITIAL.
        LV_ERROR = 'X'.
        XKOMFK-FXMSG = '007'.
        PERFORM VBFS_HINZUFUEGEN_ALLG IN PROGRAM SAPLV60A
                  USING XVBRP-VGBEL XVBRP-VGPOS
                        'ZSD01' 'E' 007
                        SPACE SPACE
                        SPACE SPACE.
      ENDIF.
    ENDLOOP.
    IF LV_ERROR IS NOT INITIAL.
      LOOP AT XVBRP INTO DATA(LS_XVBRP) WHERE VBELN = XVBRK-VBELN.
        DELETE XVBUK WHERE VBELN = LS_XVBRP-VGBEL.
        DELETE XVBUP WHERE VBELN = LS_XVBRP-VGBEL
                       AND POSNR = LS_XVBRP-VGPOS.
        DELETE XVBFA WHERE VBELV = LS_XVBRP-VGBEL
                       AND POSNV = LS_XVBRP-VGPOS.
      ENDLOOP.
      DELETE XVBRP WHERE VBELN = XVBRK-VBELN.
      DELETE XVBPA WHERE VBELN = XVBRK-VBELN.
      DELETE XKOMV WHERE KNUMV = XVBRK-VBELN.
      DELETE XVBRK WHERE VBELN = XVBRK-VBELN.
    ENDIF.
  ENDLOOP.

ENDENHANCEMENT.
*$*$-End:   (1)---------------------------------------------------------------------------------$*$*
ENDFORM.