REPORT {{p}}repair_list. PARAMETERS p_date TYPE d OBLIGATORY. CLASS lcl_report DEFINITION FINAL. PUBLIC SECTION. TYPES: BEGIN OF ty_line, job_id TYPE {{p}}jobhdr-job_id, customer TYPE {{p}}jobhdr-customer, mechanic TYPE {{p}}mech-mech_name, items TYPE i, total TYPE {{p}}jobitm-price, END OF ty_line, tt_line TYPE STANDARD TABLE OF ty_line WITH EMPTY KEY. METHODS run IMPORTING iv_date TYPE d RETURNING VALUE(rt_lines) TYPE tt_line. METHODS display CHANGING ct_lines TYPE tt_line. PRIVATE SECTION. CONSTANTS c_unknown TYPE {{p}}mech-mech_name VALUE 'UNKNOWN'. TYPES: BEGIN OF ty_job, job_id TYPE {{p}}jobhdr-job_id, customer TYPE {{p}}jobhdr-customer, mech_id TYPE {{p}}jobhdr-mech_id, END OF ty_job, tt_job TYPE SORTED TABLE OF ty_job WITH UNIQUE KEY job_id. TYPES: BEGIN OF ty_sum, job_id TYPE {{p}}jobhdr-job_id, items TYPE i, total TYPE {{p}}jobitm-price, END OF ty_sum, tt_sum TYPE SORTED TABLE OF ty_sum WITH UNIQUE KEY job_id. TYPES: BEGIN OF ty_mech, mech_id TYPE {{p}}mech-mech_id, mech_name TYPE {{p}}mech-mech_name, END OF ty_mech, tt_mech TYPE SORTED TABLE OF ty_mech WITH UNIQUE KEY mech_id. TYPES tt_job_range TYPE RANGE OF {{p}}jobhdr-job_id. TYPES tt_mech_range TYPE RANGE OF {{p}}mech-mech_id. METHODS read_jobs IMPORTING iv_date TYPE d RETURNING VALUE(rt_jobs) TYPE tt_job. METHODS job_range IMPORTING it_jobs TYPE tt_job RETURNING VALUE(rt_range) TYPE tt_job_range. METHODS mech_range IMPORTING it_jobs TYPE tt_job RETURNING VALUE(rt_range) TYPE tt_mech_range. METHODS read_sums IMPORTING it_job_range TYPE tt_job_range RETURNING VALUE(rt_sums) TYPE tt_sum. METHODS read_mechanics IMPORTING it_mech_range TYPE tt_mech_range RETURNING VALUE(rt_mech) TYPE tt_mech. ENDCLASS. CLASS lcl_report IMPLEMENTATION. METHOD run. DATA(lt_jobs) = read_jobs( iv_date ). IF lt_jobs IS INITIAL. RETURN. ENDIF. DATA(lt_sums) = read_sums( job_range( lt_jobs ) ). DATA(lt_mech) = read_mechanics( mech_range( lt_jobs ) ). LOOP AT lt_jobs INTO DATA(ls_job). READ TABLE lt_sums INTO DATA(ls_sum) WITH TABLE KEY job_id = ls_job-job_id. IF sy-subrc <> 0. CONTINUE. ENDIF. READ TABLE lt_mech INTO DATA(ls_mech) WITH TABLE KEY mech_id = ls_job-mech_id. DATA(lv_mechanic) = COND {{p}}mech-mech_name( WHEN sy-subrc = 0 THEN ls_mech-mech_name ELSE c_unknown ). INSERT VALUE #( job_id = ls_job-job_id customer = ls_job-customer mechanic = lv_mechanic items = ls_sum-items total = ls_sum-total ) INTO TABLE rt_lines. ENDLOOP. SORT rt_lines BY total DESCENDING job_id ASCENDING. ENDMETHOD. METHOD read_jobs. SELECT job_id, customer, mech_id FROM {{p}}jobhdr WHERE job_date = @iv_date INTO CORRESPONDING FIELDS OF TABLE @rt_jobs. ENDMETHOD. METHOD job_range. rt_range = VALUE #( FOR ls_job IN it_jobs ( sign = 'I' option = 'EQ' low = ls_job-job_id ) ). ENDMETHOD. METHOD mech_range. rt_range = VALUE #( FOR ls_job IN it_jobs ( sign = 'I' option = 'EQ' low = ls_job-mech_id ) ). ENDMETHOD. METHOD read_sums. SELECT job_id, qty, price FROM {{p}}jobitm WHERE job_id IN @it_job_range INTO TABLE @DATA(lt_items). LOOP AT lt_items INTO DATA(ls_item). READ TABLE rt_sums ASSIGNING FIELD-SYMBOL() WITH TABLE KEY job_id = ls_item-job_id. IF sy-subrc <> 0. INSERT VALUE #( job_id = ls_item-job_id ) INTO TABLE rt_sums ASSIGNING . ENDIF. -items = -items + 1. -total = -total + ls_item-qty * ls_item-price. ENDLOOP. ENDMETHOD. METHOD read_mechanics. SELECT mech_id, mech_name FROM {{p}}mech WHERE mech_id IN @it_mech_range INTO CORRESPONDING FIELDS OF TABLE @rt_mech. ENDMETHOD. METHOD display. TRY. cl_salv_table=>factory( IMPORTING r_salv_table = DATA(lo_alv) CHANGING t_table = ct_lines ). lo_alv->display( ). CATCH cx_salv_msg INTO DATA(lx_error). MESSAGE lx_error TYPE 'I' DISPLAY LIKE 'E'. ENDTRY. ENDMETHOD. ENDCLASS. START-OF-SELECTION. DATA(go_report) = NEW lcl_report( ). DATA(gt_lines) = go_report->run( p_date ). go_report->display( CHANGING ct_lines = gt_lines ). CLASS ltc_report DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. PRIVATE SECTION. CLASS-DATA go_env TYPE REF TO if_osql_test_environment. CLASS-METHODS class_setup. CLASS-METHODS class_teardown. METHODS totals_and_items FOR TESTING. METHODS unknown_mechanic FOR TESTING. METHODS skips_jobs_without_items FOR TESTING. ENDCLASS. CLASS ltc_report IMPLEMENTATION. METHOD class_setup. go_env = cl_osql_test_environment=>create( i_dependency_list = VALUE #( ( '{{P}}MECH' ) ( '{{P}}JOBHDR' ) ( '{{P}}JOBITM' ) ) ). DATA lt_mech TYPE STANDARD TABLE OF {{p}}mech WITH EMPTY KEY. lt_mech = VALUE #( ( mech_id = 'M1' mech_name = 'Anna' ) ). go_env->insert_test_data( lt_mech ). DATA lt_job TYPE STANDARD TABLE OF {{p}}jobhdr WITH EMPTY KEY. lt_job = VALUE #( ( job_id = 1 job_date = '20260410' customer = 'A' mech_id = 'M1' ) ( job_id = 2 job_date = '20260410' customer = 'B' mech_id = 'M9' ) ( job_id = 3 job_date = '20260410' customer = 'C' mech_id = 'M1' ) ). go_env->insert_test_data( lt_job ). DATA lt_item TYPE STANDARD TABLE OF {{p}}jobitm WITH EMPTY KEY. lt_item = VALUE #( ( job_id = 1 item_no = 10 spare_part = 'X' qty = 2 price = 10 ) ( job_id = 1 item_no = 20 spare_part = 'Y' qty = 1 price = 5 ) ( job_id = 2 item_no = 10 spare_part = 'Z' qty = 1 price = 40 ) ). go_env->insert_test_data( lt_item ). ENDMETHOD. METHOD class_teardown. go_env->destroy( ). ENDMETHOD. METHOD totals_and_items. DATA(lt_lines) = NEW lcl_report( )->run( '20260410' ). cl_abap_unit_assert=>assert_equals( exp = 2 act = lines( lt_lines ) ). cl_abap_unit_assert=>assert_equals( exp = 'B' act = lt_lines[ 1 ]-customer ). cl_abap_unit_assert=>assert_equals( exp = CONV {{p}}jobitm-price( 40 ) act = lt_lines[ 1 ]-total ). cl_abap_unit_assert=>assert_equals( exp = 2 act = lt_lines[ 2 ]-items ). cl_abap_unit_assert=>assert_equals( exp = CONV {{p}}jobitm-price( 25 ) act = lt_lines[ 2 ]-total ). ENDMETHOD. METHOD unknown_mechanic. DATA(lt_lines) = NEW lcl_report( )->run( '20260410' ). cl_abap_unit_assert=>assert_equals( exp = 'UNKNOWN' act = lt_lines[ 1 ]-mechanic ). ENDMETHOD. METHOD skips_jobs_without_items. DATA(lt_lines) = NEW lcl_report( )->run( '20260410' ). cl_abap_unit_assert=>assert_equals( exp = 2 act = lines( lt_lines ) ). ENDMETHOD. ENDCLASS.