* _______ _______ _______ _______ _______ _______ ______ _______ _______ ______ _______ * | _ | _ | _ | | | _ | || _ | _ | _ | | _ | * | |_| | |_| | |_| | _ | | |_| | _ | |_| | |_| | | || | |_| | * | | | | |_| | | | | | | | | |_||_| | * | | _ || | ___| _| | |_| | | _ || __ | | * | _ | |_| | _ | | | |_| _ | | _ | |_| | | | | _ | * |__| |__|_______|__| |__|___| |_______|__| |__|______||__| |__|_______|___| |_|__| |__| * www.abapcadabra.com *------------------------------------------------------------------------------------------- * program : ZABAPCADABRA_CUSTOMER2CSV * title : Outgoing interface - customer data to a .CSV * functional area : MM/SD * environment : 4.7 * program Function : This report creates a full-download of customer data in 2 * CSV files, on the server. * The report demonstrates a simple interface with Business * Application Log logging and a shadow check mechanism based * on table ZSHADOW_INDX. * Fields on ZSHADOW_INDEX: * MANDT type MANDT (key) * RELID type INDX_RELID (key) * SRTFD type INDX_SRTFD (key) * SRTF2 type INDX_SRTF2 (key) * CREATE_DATE type SYDATS * CREATE_TIME type SYTIME * CHANGE_DATE type SYDATS * CHANGE_TIME type SYTIME * PROGNAME type PROGNAME * CLUSTR type INDX_CLSTR * CLUSTD type INDX_CLUST * Previous version : This is the initial version * Developer name : Wim Maasdam * Development date : 22nd of March 2017 * Version : 0.1 *--------------------------------------------------------------------- * Change list: * Date Description * 22/03/2017 Initial version (demonstration version) *--------------------------------------------------------------------- REPORT ZABAPCADABRA_CUSTOMER2CSV. TABLES: KNA1, KNB1, sscrfields, "Selection screen purpose only ZSHADOW_INDX. "INDX fields purpose *--------------------------------------------------------------------- * C L A S S D E F I N I T I O N *--------------------------------------------------------------------- CLASS lcl_controller DEFINITION FINAL. PUBLIC SECTION. TYPES: begin of ty_kna1, kunnr type kna1-kunnr, name1 type kna1-name1, ktokd type kna1-ktokd, stras type kna1-stras, pstlz type kna1-pstlz, ort01 type kna1-ort01, telf1 type kna1-telf1, adrnr type kna1-adrnr, regio type kna1-regio, stcd1 type kna1-stcd1, stceg type kna1-stceg, erdat type kna1-erdat, loevm type kna1-loevm, nodel type kna1-nodel, sperr type kna1-sperr, aufsd type kna1-aufsd, lifsd type kna1-lifsd, faksd type kna1-faksd, cassd type kna1-cassd, end of ty_kna1, begin of ty_adr6, addrnumber type adr6-addrnumber, consnumber type adr6-consnumber, smtp_addr type adr6-smtp_addr, end of ty_adr6, begin of ty_knb1, kunnr type knb1-kunnr, bukrs type knb1-bukrs, zterm type knb1-zterm, zwels type knb1-zwels, knrzb type knb1-knrzb, zahls type knb1-zahls, akont type knb1-akont, fdgrv type knb1-fdgrv, end of ty_knb1. CLASS-DATA: gr_bukrs type range of knb1-bukrs, gr_kunnr type range of kna1-kunnr, gr_loevm type range of kna1-loevm, gv_customers_requested type boolean, gv_customer_coco_requested type boolean, * The main data containers for the interface: gt_kna1 type SORTED TABLE OF ty_kna1 WITH UNIQUE KEY kunnr, "Klantstam (algemeen gedeelte) gt_adr6 type SORTED TABLE OF ty_adr6 WITH UNIQUE KEY addrnumber consnumber, "Adresgegevens gt_knb1 type SORTED TABLE OF ty_knb1 WITH UNIQUE KEY kunnr bukrs. "Klantstam (bedrijfsnummer) CLASS-METHODS: main_selection, process_customers importing filename type string, process_customer_coco importing filename type string. ENDCLASS. CLASS lcl_shadow DEFINITION FINAL. PUBLIC SECTION. types: begin of ty_masterdata, kunnr type kna1-kunnr, content type string, end of ty_masterdata, begin of ty_coco, bukrs type knb1-bukrs, content type string, end of ty_coco, begin of ty_shadow, kunnr type kna1-kunnr, masterdata type ty_masterdata, coco type STANDARD TABLE OF ty_coco with default key, end of ty_shadow. CLASS-DATA: gv_CHECK type boolean, gv_UPDATE type boolean, gv_RESEND_DATE type d, gw_shadow type ty_shadow, "Work area gw_shadow_indx type zshadow_indx, gr_kunnr type range of kna1-kunnr. CLASS-METHODS: reset, read importing kunnr type kna1-kunnr, save. ENDCLASS. *---------------------------------------------------------------------- * CLASS lcl_logging - the message logging and error logging for this * interface is done in the Business Application Log (trx. SLG0, SLG1). * The log is displayed at the end of the report run. Logs can also * be saved (options on the selection screen are available). *---------------------------------------------------------------------- CLASS lcl_logging DEFINITION FINAL. PUBLIC SECTION. CLASS-DATA: go_log TYPE REF TO cl_ishmed_bal, gv_errors_were_logged TYPE boolean VALUE space. CLASS-METHODS: initialize IMPORTING object TYPE balobj_d DEFAULT 'ALERT' subobject TYPE balsubobj DEFAULT 'PROCESSING', set_subject IMPORTING subject TYPE any, set_message IMPORTING message TYPE any OPTIONAL par1 TYPE any DEFAULT space par2 TYPE any DEFAULT space par3 TYPE any DEFAULT space msgty TYPE symsgty DEFAULT 'I' PREFERRED PARAMETER message, set_error IMPORTING message TYPE any OPTIONAL par1 TYPE any DEFAULT space par2 TYPE any DEFAULT space PREFERRED PARAMETER message. ENDCLASS. "lcl_logging DEFINITION *---------------------------------------------------------------------- * CLASS lcl_logging IMPLEMENTATION *---------------------------------------------------------------------- CLASS lcl_logging IMPLEMENTATION. METHOD initialize. TRY. CREATE OBJECT go_log EXPORTING i_object = object i_subobject = subobject i_repid = sy-repid. CATCH cx_ishmed_log. "#EC NO_HANDLER * No actual processing here ENDTRY. ENDMETHOD. "initialize METHOD set_subject. DATA: lv_subject TYPE c LENGTH 100. lv_subject = subject. * concatenate '==>' lv_subject into lv_subject SEPARATED BY space. TRANSLATE lv_subject TO UPPER CASE. TRY. go_log->add_free_text( EXPORTING i_msg_type = 'W' i_text = lv_subject ). CATCH cx_ishmed_log. "#EC NO_HANDLER * No actual logic on catch ENDTRY. ENDMETHOD. "set_subject METHOD set_message. " importing message type any, par1, par2 DATA: lv_message TYPE c LENGTH 100. lv_message = message. REPLACE '&' WITH par1 INTO lv_message. CONDENSE lv_message. REPLACE '&' WITH par2 INTO lv_message. CONDENSE lv_message. REPLACE '&' WITH par3 INTO lv_message. CONDENSE lv_message. TRY. go_log->add_free_text( EXPORTING i_msg_type = msgty i_text = lv_message ). CATCH cx_ishmed_log. "#EC NO_HANDLER * No actual logic on catch ENDTRY. ENDMETHOD. "set_message METHOD set_error. " importing message type any, par1, par2 DATA: lv_message TYPE c LENGTH 100. lv_message = message. REPLACE '&' WITH par1 INTO lv_message. REPLACE '&' WITH par2 INTO lv_message. CONDENSE lv_message. TRY. go_log->add_free_text( EXPORTING i_msg_type = 'E' i_text = lv_message ). lcl_logging=>gv_errors_were_logged = abap_true. CATCH cx_ishmed_log. "#EC NO_HANDLER * No actual logic on catch ENDTRY. ENDMETHOD. "set_error ENDCLASS. "lcl_logging IMPLEMENTATION *--------------------------------------------------------------------- * C L A S S I M P L E M E N T A T I O N *--------------------------------------------------------------------- CLASS lcl_controller IMPLEMENTATION. METHOD main_selection. data: lv_numcount type n length 6, lw_kna1 type ty_kna1. * This method performs ALL selection relevant to the setup. clear: gt_kna1[], gt_ADR6, gt_knb1[]. lcl_logging=>set_message( 'Selection of customer data' ). * Field names specified to increase performance, 179 fields on KNA1 select * from KNA1 "Klantstam (algemeen gedeelte) into corresponding fields of table gt_kna1 where kunnr in gr_kunnr and loevm in gr_loevm. if not gt_kna1[] is initial. * The GT_kna1 listing is used as a basis for all futher KUNNR selections: select * from KNB1 "Klantstam (bedrijfsnummer) into corresponding fields of table gt_knb1 for all entries in gt_kna1 where kunnr = gt_kna1-kunnr and bukrs in gr_bukrs. * Clear irrelevant entries fromm the KNA1 main table: loop at gt_kna1 into lw_kna1. read table gt_knb1 with key kunnr = lw_kna1-kunnr TRANSPORTING NO FIELDS. if sy-subrc <> 0. delete gt_kna1. endif. endloop. endif. if not gt_kna1[] is initial. * The email addresses reside on ADR6: select addrnumber consnumber smtp_addr from adr6 into table gt_adr6 for all entries in gt_kna1 where addrnumber = gt_kna1-adrnr and persnumber = ''. endif. if not gt_kna1[] is initial. describe table gt_kna1 lines lv_numcount. lcl_logging=>set_message( message = '& customers (KNA1)' par1 = lv_numcount ). describe table gt_knb1 lines lv_numcount. lcl_logging=>set_message( message = '& customer / company codes (KNB1)' par1 = lv_numcount ). else. lcl_logging=>set_message( 'No data selected' ). endif. ENDMETHOD. METHOD process_customers. data: lv_headerline type string value 'Customer;Name;Street;Postcode;City;Province;;Telephonee-mail;Account group;TaxNr 1;VAT', lv_headerline2 type string value 'KUNNR;NAME1;STRAS;PSTLZ;ORT01;REGIO;TELF1;EMAIL;KTOKD;STCD1;STCEG', lw_kna1 type ty_kna1, lv_line_on_file type string, begin of lw_extra_fields, address type string, adr6 type ty_adr6, end of lw_extra_fields, lv_written type n length 6. * Logging if lcl_controller=>gv_customers_requested = abap_false. lcl_logging=>set_message( 'Customer file: not processed' ). exit. endif. lcl_logging=>set_message( 'Process customer file' ). lcl_logging=>set_message( filename ). * Open file, get ready to write output: OPEN DATASET filename FOR OUTPUT IN TEXT MODE ENCODING DEFAULT. if sy-subrc <> 0. lcl_logging=>set_error( 'File could not be opened' ). else. * Set header line: transfer lv_headerline to filename. transfer lv_headerline2 to filename. clear: lv_written. loop at gt_kna1 into lw_kna1. clear: lv_line_on_file, lw_extra_fields. * Prepare the fields that require preperation: read table gt_adr6 with key addrnumber = lw_kna1-adrnr into lw_extra_fields-adr6. if sy-subrc <> 0. clear lw_extra_fields-adr6. endif. concatenate lw_kna1-kunnr "KUNNR lw_kna1-name1 "NAME1 lw_kna1-stras "STRAS lw_kna1-pstlz "PSTLZ lw_kna1-ort01 "ORT01 lw_kna1-regio "REGIO lw_kna1-telf1 "TELF1 lw_extra_fields-adr6-smtp_addr "EMAIL lw_kna1-ktokd "KTOKD lw_kna1-stcd1 "STCD1 lw_kna1-stceg "STCEG into lv_line_on_file SEPARATED BY ';'. * Shadow checks: read the information that has been sent out before: lcl_shadow=>read( kunnr = lw_kna1-kunnr ). if lcl_shadow=>gw_shadow-masterdata-content <> lv_line_on_file. *------------------------------------------------------------------------- transfer lv_line_on_file to filename. *------------------------------------------------------------------------- lcl_shadow=>gw_shadow-masterdata-content = lv_line_on_file. lcl_shadow=>save( ). add 1 to lv_written. endif. endloop. "KNA1 CLOSE DATASET filename. lcl_logging=>set_message( message = '& lines added to the file' par1 = lv_written ). endif. ENDMETHOD. METHOD process_customer_coco. data: lv_headerline type string value 'Customer;Company;Paymentconditions;Payment methods;Bank number(2);Blockcode;Accountnumber;Fin.displogroup', lv_headerline2 type string value 'KUNNR;BUKRS;ZTERM;ZWELS;KNRZB;ZAHLS;AKONT;FDGRV', lw_kna1 type ty_kna1, lw_knb1 type ty_knb1, lv_line_on_file type string, lv_written type n length 6, lv_shadow_update_required type boolean, lw_coco type lcl_shadow=>ty_coco, lv_coco_index type sy-tabix. * Logging if lcl_controller=>gv_customers_requested = abap_false. lcl_logging=>set_message( 'Customer file: not processed' ). exit. endif. lcl_logging=>set_message( 'Process customer file' ). lcl_logging=>set_message( filename ). * Open file, get ready to write output: OPEN DATASET filename FOR OUTPUT IN TEXT MODE ENCODING DEFAULT. if sy-subrc <> 0. lcl_logging=>set_error( 'File could not be opened' ). else. * Set header line: transfer lv_headerline to filename. transfer lv_headerline2 to filename. clear: lv_written. loop at gt_kna1 into lw_kna1. * Shadow checks: read the information that has been sent out before: lcl_shadow=>read( kunnr = lw_kna1-kunnr ). clear lv_shadow_update_required. loop at gt_knb1 into lw_knb1 where kunnr = lw_kna1-kunnr. clear: lv_line_on_file. concatenate lw_knb1-kunnr "KUNNR lw_knb1-bukrs "BUKRS lw_knb1-zterm "ZTERM lw_knb1-zwels "ZWELS lw_knb1-knrzb "KNRZB lw_knb1-zahls "ZAHLS lw_knb1-akont "AKONT lw_knb1-fdgrv "FDGRV into lv_line_on_file SEPARATED BY ';'. read table lcl_shadow=>gw_shadow-coco into lw_coco with key bukrs = lw_knb1-bukrs. if sy-subrc <> 0. *------------------------------------------------------------------------- transfer lv_line_on_file to filename. *------------------------------------------------------------------------- lw_coco-bukrs = lw_knb1-bukrs. lw_coco-content = lv_line_on_file. append lw_coco to lcl_shadow=>gw_shadow-coco. add 1 to lv_written. elseif lw_coco-content <> lv_line_on_file. lv_coco_index = sy-tabix. *------------------------------------------------------------------------- transfer lv_line_on_file to filename. *------------------------------------------------------------------------- lw_coco-content = lv_line_on_file. modify lcl_shadow=>gw_shadow-coco from lw_coco index lv_coco_index. add 1 to lv_written. endif. endloop. "KNB1 if lv_shadow_update_required = abap_true. lcl_shadow=>save( ). endif. endloop. "KNA1 CLOSE DATASET filename. lcl_logging=>set_message( message = '& lines added to the file' par1 = lv_written ). endif. ENDMETHOD. ENDCLASS. CLASS lcl_shadow IMPLEMENTATION. method reset. data: lv_answer type c length 1, lt_SRTFD type standard table of INDX_SRTFD, lv_SRTFD type INDX_SRTFD, begin of lv_SRTFD_fields, dummy type c length 10, kunnr type c length 10, end of lv_SRTFD_fields. * This method will actively remove entries from the shadow tables, which is * "backdoor" funtionality used for: * - Changes in the actual shadow table structure (ty_shadow, avoid dumps) * - Testing purpose (and that does NOT include UAT) * - Free up the database when this interface is no longer being used CALL FUNCTION 'POPUP_TO_CONFIRM' EXPORTING TEXT_QUESTION = 'Do you want to remove (selected) shadow table data ?' IMPORTING ANSWER = lv_answer. if LV_ANSWER = '1'. * Compose keys and remove ZSHADOW_INDX entries select SRTFD from ZSHADOW_INDX into table lt_SRTFD where relid = 'DB' and SRTFD like 'KUNNR~%'. * Filter out the keys that should not be deleted (align with select-options) loop at lt_SRTFD into lv_SRTFD. split lv_SRTFD at '~' into lv_SRTFD_fields-dummy lv_SRTFD_fields-kunnr. if not ( lv_SRTFD_fields-kunnr in gr_kunnr ). delete lt_SRTFD. endif. endloop. if lt_SRTFD[] is initial. message 'No data removed (no matches)' type 'S'. else. loop at lt_SRTFD into lv_SRTFD. delete from database ZSHADOW_INDX(DB) id lv_SRTFD. endloop. message 'Shadow table data removed' type 'S'. endif. endif. endmethod. method read. data: lv_SRTFD type INDX_SRTFD. clear: gw_shadow, gw_shadow-coco[]. concatenate 'KUNNR' kunnr into lv_SRTFD separated by '~'. if gv_CHECK = abap_true. select SINGLE CREATE_DATE CREATE_TIME CHANGE_DATE CHANGE_TIME PROGNAME from ZSHADOW_INDX into CORRESPONDING FIELDS OF gw_shadow_indx where relid = 'DB' and SRTFD = lv_SRTFD and SRTF2 = 0. if sy-subrc = 0. if not gv_RESEND_DATE is initial. if gv_RESEND_DATE < gw_shadow_indx-CHANGE_DATE. import gw_shadow = gw_shadow from database ZSHADOW_INDX(DB) id lv_SRTFD. endif. else. import gw_shadow = gw_shadow from database ZSHADOW_INDX(DB) id lv_SRTFD. endif. else. clear gw_shadow_indx. endif. endif. gw_shadow-kunnr = kunnr. endmethod. method save. data: lv_SRTFD type INDX_SRTFD. if gv_UPDATE = abap_true. if gw_shadow_indx-CREATE_DATE is initial. gw_shadow_indx-CREATE_DATE = sy-datum. gw_shadow_indx-CREATE_TIME = sy-uzeit. gw_shadow_indx-PROGNAME = sy-repid. else. gw_shadow_indx-CHANGE_DATE = sy-datum. gw_shadow_indx-CHANGE_TIME = sy-uzeit. endif. concatenate 'KUNNR' gw_shadow-kunnr into lv_SRTFD separated by '~'. MOVE-CORRESPONDING gw_shadow_indx to ZSHADOW_INDX. export gw_shadow = gw_shadow to database ZSHADOW_INDX(DB) id lv_SRTFD. if sy-subrc <> 0. lcl_logging=>set_error( message = 'Schaduw tabel bijwerken: fout opgetreden (&)' par1 = lv_SRTFD ). endif. endif. endmethod. ENDCLASS. *--------------------------------------------------------------------- * S E L E C T I O N - S C R E E N *--------------------------------------------------------------------- SELECTION-SCREEN BEGIN OF BLOCK b01 WITH FRAME TITLE text-t01. SELECT-OPTIONS: so_kunnr for kna1-kunnr, so_bukrs for knb1-bukrs, so_loevm for kna1-loevm. SELECTION-SCREEN END OF BLOCK b01. SELECTION-SCREEN BEGIN OF BLOCK b02 WITH FRAME TITLE text-t02. SELECTION-SCREEN: BEGIN OF LINE. PARAMETERS: pa_debyn AS CHECKBOX default abap_true. SELECTION-SCREEN: COMMENT 5(75) lbl_02 FOR FIELD pa_debyn, END OF LINE. parameters: pa_debfi TYPE string LOWER CASE modif id reo. SELECTION-SCREEN END OF BLOCK b02. SELECTION-SCREEN BEGIN OF BLOCK b03 WITH FRAME TITLE text-t03. SELECTION-SCREEN: BEGIN OF LINE. PARAMETERS: pa_facyn AS CHECKBOX default abap_true. SELECTION-SCREEN: COMMENT 5(75) lbl_03 FOR FIELD pa_facyn, END OF LINE. parameters: pa_facfi TYPE string LOWER CASE modif id reo. SELECTION-SCREEN END OF BLOCK b03. parameters: pa_lfile type FILENAME-FILEINTERN default 'ZINTERFACE_CUSTOMERS' modif id reo. SELECTION-SCREEN: SKIP, BEGIN OF LINE, COMMENT 1(33) lbl_s1. PARAMETERS: pa_shaco AS CHECKBOX default abap_true. SELECTION-SCREEN: COMMENT 38(14) lbl_s2 FOR FIELD pa_shaco. parameters: pa_shaup AS CHECKBOX default abap_true. SELECTION-SCREEN: COMMENT 56(20) lbl_s3 FOR FIELD pa_shaup, END OF LINE, BEGIN OF LINE, COMMENT 1(33) lbl_s4. parameters: pa_share type d. SELECTION-SCREEN: END OF lINE. AT SELECTION-SCREEN OUTPUT. loop at screen. if screen-group1 = 'REO'. screen-input = 0. modify screen. endif. endloop. * Get the name for the debiteuren file: CALL FUNCTION 'FILE_GET_NAME' EXPORTING LOGICAL_FILENAME = pa_lfile PARAMETER_1 = 'customermaster' IMPORTING FILE_NAME = pa_debfi EXCEPTIONS OTHERS = 0. * Customer company codes CALL FUNCTION 'FILE_GET_NAME' EXPORTING LOGICAL_FILENAME = pa_lfile PARAMETER_1 = 'customercoco' IMPORTING FILE_NAME = pa_facfi EXCEPTIONS OTHERS = 0. AT SELECTION-SCREEN. CASE sscrfields-ucomm. * Please note: this function code is not made available on the selection screen * for an end user to find. It is for maintenance purpose only, allowing the * setup to be taken out of commission or to accomodate test cycles. This is * "hidden" functionality. WHEN 'RESET_SHADOW'. lcl_shadow=>gr_kunnr[] = so_kunnr[]. lcl_shadow=>reset( ). ENDCASE. *--------------------------------------------------------------------- * I N I T I A L I Z A T I O N *--------------------------------------------------------------------- INITIALIZATION. lbl_02 = 'Customer data: compose and send'. lbl_03 = 'Customer company codes: compose and send'. lbl_s1 = 'Shadow table (change control)'. lbl_s2 = 'Check'. lbl_s3 = 'Update'. lbl_s4 = 'Resend since'. *--------------------------------------------------------------------- * S T A R T - O F - S E L E C T I O N *--------------------------------------------------------------------- START-OF-SELECTION. if not pa_share is initial and pa_shaup = abap_true. message 'Resend date: update not allowed' type 'S'. elseif not pa_share is initial and pa_shaco = abap_false. message 'Resend date: check is mandatory' type 'S'. else. CALL FUNCTION 'ENQUEUE_EPROG' EXPORTING PROGRAMM = sy-repid EXCEPTIONS OTHERS = 1. if sy-subrc <> 0. message 'Report is already running.... ' type 'S'. else. lcl_logging=>initialize( ). lcl_logging=>set_subject( 'Start of the outbound interface run' ). AUTHORITY-CHECK OBJECT 'S_TCODE' ID 'TCD' FIELD 'ZI_CUSTOMER'. IF SY-SUBRC <> 0. message 'You are not authorized to run this report' type 'S'. leave program. ENDIF. * Hand the selectio options to the controller class: lcl_controller=>gr_bukrs[] = so_bukrs[]. lcl_controller=>gr_kunnr[] = so_kunnr[]. lcl_controller=>gr_loevm[] = so_loevm[]. lcl_controller=>gv_customers_requested = pa_debyn. lcl_controller=>gv_customer_coco_requested = pa_facyn. lcl_shadow=>gv_CHECK = pa_shaco. lcl_shadow=>gv_UPDATE = pa_shaup. lcl_shadow=>gv_RESEND_DATE = pa_share. * Start off the main processing: selection of data lcl_controller=>main_selection( ). * Compose the file and save it: debiteuren lcl_controller=>process_customers( pa_debfi ). lcl_controller=>process_customer_coco( pa_facfi ). lcl_logging=>set_subject( 'Outbound interface finished' ). CALL FUNCTION 'DEQUEUE_ALL'. lcl_logging=>go_log->display( ). if lcl_logging=>GV_ERRORS_WERE_LOGGED = abap_true and sy-batch = abap_true. message 'Errors found in outbound interface - please check log' type 'E'. endif. endif. endif.