Generator: pilot analysis and fixes; G0002-G0021 tasks; handover notes merged

- Static bundle checks (name length, seed type, reserved words, contract test classes,
  testclasses_file), local abaplint parser check before SAP, max_tokens, robust JSON parse
- Runner: G2 for CDS views with parameters, contract_check detail in report
- G0020 and G0013 fixed by review and revalidated (oracle 100, null 0)
- Pilot results in docs/faz1-tasarim.md 11e; handover notes merged into CLAUDE.md and docs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kral
2026-10-03 05:04:23 +02:00
parent 15eb9f1bb2
commit 5de5e2851c
162 changed files with 9943 additions and 23 deletions

View File

@@ -0,0 +1,29 @@
{
"id": "G0003",
"pool": "eval",
"object_type": "CLAS",
"category": "B",
"attempts": [
{
"stage": "validate",
"oracle": null,
"null": null
},
{
"stage": "validate",
"oracle": null,
"null": null
},
{
"stage": "validate",
"oracle": 0,
"null": 0
},
{
"stage": "validate",
"oracle": 100.0,
"null": 0
}
],
"accepted": true
}

View File

@@ -0,0 +1,119 @@
CLASS {{P}}T02_HIDDEN DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA mo_environment TYPE REF TO if_osql_test_environment.
DATA mo_cut TYPE REF TO {{P}}COURSE_BOOKING.
CLASS-METHODS class_setup.
CLASS-METHODS class_teardown.
METHODS setup.
METHODS fill_test_data.
METHODS free_seats_open_course FOR TESTING.
METHODS free_seats_never_neg FOR TESTING.
METHODS free_seats_unknown FOR TESTING.
METHODS bookable_exact_free FOR TESTING.
METHODS not_bookable_too_many FOR TESTING.
METHODS not_bookable_unknown FOR TESTING.
METHODS not_bookable_zero_seats FOR TESTING.
METHODS list_filter_cat_seats FOR TESTING.
METHODS list_sorted_by_course FOR TESTING.
METHODS list_empty_without_match FOR TESTING.
METHODS list_excludes_full FOR TESTING.
ENDCLASS.
CLASS {{P}}T02_HIDDEN IMPLEMENTATION.
METHOD class_setup.
mo_environment = cl_osql_test_environment=>create( VALUE #( ( '{{P}}COURSE' ) ) ).
ENDMETHOD.
METHOD class_teardown.
mo_environment->destroy( ).
ENDMETHOD.
METHOD setup.
mo_environment->clear_doubles( ).
fill_test_data( ).
mo_cut = NEW {{P}}COURSE_BOOKING( ).
ENDMETHOD.
METHOD fill_test_data.
DATA lt_course TYPE STANDARD TABLE OF {{P}}COURSE WITH EMPTY KEY.
lt_course = VALUE #(
( mandt = sy-mandt course_id = 'ABAP101' category = 'IT' title = 'ABAP Basics' capacity = 20 booked = 5 )
( mandt = sy-mandt course_id = 'ABAP102' category = 'IT' title = 'ABAP Advanced' capacity = 10 booked = 10 )
( mandt = sy-mandt course_id = 'ABAP103' category = 'IT' title = 'ABAP Expert' capacity = 12 booked = 15 )
( mandt = sy-mandt course_id = 'SQL101' category = 'DB' title = 'SQL Basics' capacity = 30 booked = 10 )
( mandt = sy-mandt course_id = 'SQL102' category = 'DB' title = 'SQL Tuning' capacity = 8 booked = 8 )
( mandt = sy-mandt course_id = 'HR101' category = 'HR' title = 'HR Basics' capacity = 6 booked = 1 ) ).
INSERT {{P}}COURSE FROM TABLE @lt_course.
ENDMETHOD.
METHOD free_seats_open_course.
cl_abap_unit_assert=>assert_equals( exp = 15
act = mo_cut->get_free_seats( 'ABAP101' )
msg = 'ABAP101 must have 15 free seats' ).
ENDMETHOD.
METHOD free_seats_never_neg.
cl_abap_unit_assert=>assert_equals( exp = 0
act = mo_cut->get_free_seats( 'ABAP102' )
msg = 'full course' ).
cl_abap_unit_assert=>assert_equals( exp = 0
act = mo_cut->get_free_seats( 'ABAP103' )
msg = 'overbooked course' ).
ENDMETHOD.
METHOD free_seats_unknown.
cl_abap_unit_assert=>assert_equals( exp = 0
act = mo_cut->get_free_seats( 'XX999' ) ).
ENDMETHOD.
METHOD bookable_exact_free.
cl_abap_unit_assert=>assert_true( mo_cut->is_bookable( iv_course_id = 'SQL101' iv_seats = 20 ) ).
ENDMETHOD.
METHOD not_bookable_too_many.
cl_abap_unit_assert=>assert_false( mo_cut->is_bookable( iv_course_id = 'SQL101' iv_seats = 21 ) ).
ENDMETHOD.
METHOD not_bookable_unknown.
cl_abap_unit_assert=>assert_false( mo_cut->is_bookable( iv_course_id = 'XX999' iv_seats = 1 ) ).
ENDMETHOD.
METHOD not_bookable_zero_seats.
cl_abap_unit_assert=>assert_false( mo_cut->is_bookable( iv_course_id = 'SQL101' iv_seats = 0 ) ).
cl_abap_unit_assert=>assert_false( mo_cut->is_bookable( iv_course_id = 'SQL101' iv_seats = -5 ) ).
ENDMETHOD.
METHOD list_filter_cat_seats.
DATA(lt_result) = mo_cut->list_bookable_courses( iv_category = 'DB' iv_seats = 5 ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lines( lt_result ) ).
cl_abap_unit_assert=>assert_equals( exp = 'SQL101' act = lt_result[ 1 ]-course_id ).
cl_abap_unit_assert=>assert_equals( exp = 'SQL Basics' act = lt_result[ 1 ]-title ).
cl_abap_unit_assert=>assert_equals( exp = 20 act = lt_result[ 1 ]-free_seats ).
ENDMETHOD.
METHOD list_sorted_by_course.
DATA(lt_result) = mo_cut->list_bookable_courses( iv_category = 'IT' iv_seats = 0 ).
cl_abap_unit_assert=>assert_equals( exp = 3 act = lines( lt_result ) ).
cl_abap_unit_assert=>assert_equals( exp = 'ABAP101' act = lt_result[ 1 ]-course_id ).
cl_abap_unit_assert=>assert_equals( exp = 'ABAP102' act = lt_result[ 2 ]-course_id ).
cl_abap_unit_assert=>assert_equals( exp = 'ABAP103' act = lt_result[ 3 ]-course_id ).
cl_abap_unit_assert=>assert_equals( exp = 0 act = lt_result[ 3 ]-free_seats ).
ENDMETHOD.
METHOD list_empty_without_match.
DATA(lt_hr) = mo_cut->list_bookable_courses( iv_category = 'HR' iv_seats = 6 ).
cl_abap_unit_assert=>assert_initial( lt_hr ).
DATA(lt_unknown) = mo_cut->list_bookable_courses( iv_category = 'XX' iv_seats = 1 ).
cl_abap_unit_assert=>assert_initial( lt_unknown ).
ENDMETHOD.
METHOD list_excludes_full.
DATA(lt_result) = mo_cut->list_bookable_courses( iv_category = 'IT' iv_seats = 1 ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lines( lt_result ) ).
cl_abap_unit_assert=>assert_equals( exp = 'ABAP101' act = lt_result[ 1 ]-course_id ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,94 @@
CLASS {{P}}COURSE_BOOKING DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES ty_course_id TYPE c LENGTH 10.
TYPES ty_category TYPE c LENGTH 10.
TYPES:
BEGIN OF ty_course_info,
course_id TYPE ty_course_id,
title TYPE c LENGTH 40,
free_seats TYPE i,
END OF ty_course_info.
TYPES tt_course_info TYPE STANDARD TABLE OF ty_course_info WITH EMPTY KEY.
METHODS get_free_seats
IMPORTING iv_course_id TYPE ty_course_id
RETURNING VALUE(rv_free_seats) TYPE i.
METHODS is_bookable
IMPORTING iv_course_id TYPE ty_course_id
iv_seats TYPE i
RETURNING VALUE(rv_bookable) TYPE abap_bool.
METHODS list_bookable_courses
IMPORTING iv_category TYPE ty_category
iv_seats TYPE i
RETURNING VALUE(rt_courses) TYPE tt_course_info.
PRIVATE SECTION.
TYPES:
BEGIN OF ty_course,
course_id TYPE ty_course_id,
title TYPE c LENGTH 40,
capacity TYPE i,
booked TYPE i,
END OF ty_course.
METHODS read_course
IMPORTING iv_course_id TYPE ty_course_id
RETURNING VALUE(rs_course) TYPE ty_course.
METHODS calculate_free_seats
IMPORTING is_course TYPE ty_course
RETURNING VALUE(rv_free) TYPE i.
ENDCLASS.
CLASS {{P}}COURSE_BOOKING IMPLEMENTATION.
METHOD get_free_seats.
rv_free_seats = calculate_free_seats( read_course( iv_course_id ) ).
ENDMETHOD.
METHOD is_bookable.
IF iv_seats < 1.
rv_bookable = abap_false.
RETURN.
ENDIF.
rv_bookable = xsdbool( iv_seats <= calculate_free_seats( read_course( iv_course_id ) ) ).
ENDMETHOD.
METHOD list_bookable_courses.
DATA lt_course TYPE STANDARD TABLE OF ty_course WITH EMPTY KEY.
SELECT course_id, title, capacity, booked
FROM {{P}}COURSE
WHERE category = @iv_category
INTO TABLE @lt_course.
LOOP AT lt_course INTO DATA(ls_course).
DATA(lv_free) = calculate_free_seats( ls_course ).
IF lv_free < iv_seats.
CONTINUE.
ENDIF.
rt_courses = VALUE #( BASE rt_courses
( course_id = ls_course-course_id
title = ls_course-title
free_seats = lv_free ) ).
ENDLOOP.
SORT rt_courses BY course_id.
ENDMETHOD.
METHOD read_course.
SELECT SINGLE course_id, title, capacity, booked
FROM {{P}}COURSE
WHERE course_id = @iv_course_id
INTO @rs_course.
ENDMETHOD.
METHOD calculate_free_seats.
rv_free = is_course-capacity - is_course-booked.
IF rv_free < 0.
rv_free = 0.
ENDIF.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,63 @@
CLASS ltc_course_booking DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA mo_environment TYPE REF TO if_osql_test_environment.
DATA mo_cut TYPE REF TO {{P}}COURSE_BOOKING.
CLASS-METHODS class_setup.
CLASS-METHODS class_teardown.
METHODS setup.
METHODS fill_test_data.
METHODS free_seats_open_course FOR TESTING.
METHODS free_seats_full_course FOR TESTING.
METHODS bookable_enough_seats FOR TESTING.
METHODS not_bookable_full FOR TESTING.
METHODS list_matching_courses FOR TESTING.
ENDCLASS.
CLASS ltc_course_booking IMPLEMENTATION.
METHOD class_setup.
mo_environment = cl_osql_test_environment=>create( VALUE #( ( '{{P}}COURSE' ) ) ).
ENDMETHOD.
METHOD class_teardown.
mo_environment->destroy( ).
ENDMETHOD.
METHOD setup.
mo_environment->clear_doubles( ).
fill_test_data( ).
mo_cut = NEW {{P}}COURSE_BOOKING( ).
ENDMETHOD.
METHOD fill_test_data.
DATA lt_course TYPE STANDARD TABLE OF {{P}}COURSE WITH EMPTY KEY.
lt_course = VALUE #(
( mandt = sy-mandt course_id = 'ABAP101' category = 'IT' title = 'ABAP Basics' capacity = 20 booked = 5 )
( mandt = sy-mandt course_id = 'SQL101' category = 'DB' title = 'SQL Basics' capacity = 30 booked = 30 ) ).
INSERT {{P}}COURSE FROM TABLE @lt_course.
ENDMETHOD.
METHOD free_seats_open_course.
cl_abap_unit_assert=>assert_equals( exp = 15 act = mo_cut->get_free_seats( 'ABAP101' ) ).
ENDMETHOD.
METHOD free_seats_full_course.
cl_abap_unit_assert=>assert_equals( exp = 0 act = mo_cut->get_free_seats( 'SQL101' ) ).
ENDMETHOD.
METHOD bookable_enough_seats.
cl_abap_unit_assert=>assert_true( mo_cut->is_bookable( iv_course_id = 'ABAP101' iv_seats = 10 ) ).
ENDMETHOD.
METHOD not_bookable_full.
cl_abap_unit_assert=>assert_false( mo_cut->is_bookable( iv_course_id = 'SQL101' iv_seats = 1 ) ).
ENDMETHOD.
METHOD list_matching_courses.
DATA(lt_result) = mo_cut->list_bookable_courses( iv_category = 'IT' iv_seats = 1 ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lines( lt_result ) ).
cl_abap_unit_assert=>assert_equals( exp = 'ABAP101' act = lt_result[ 1 ]-course_id ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,8 @@
define table {{P}}COURSE {
key mandt : abap.clnt not null;
key course_id : abap.char(10) not null;
category : abap.char(10);
title : abap.char(40);
capacity : abap.int4;
booked : abap.int4;
}

View File

@@ -0,0 +1,9 @@
@EndUserText.label : 'Training courses'
define table {{P}}COURSE {
key mandt : abap.clnt not null;
key course_id : abap.char(10) not null;
category : abap.char(10);
title : abap.char(40);
capacity : abap.int4;
booked : abap.int4;
}

View File

@@ -0,0 +1,13 @@
@EndUserText.label : 'Training courses'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table {{P}}COURSE {
key mandt : abap.clnt not null;
key course_id : abap.char(10) not null;
category : abap.char(10);
title : abap.char(40);
capacity : abap.int4;
booked : abap.int4;
}

View File

@@ -0,0 +1,24 @@
CLASS {{P}}SEED_COURSES DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS {{P}}SEED_COURSES IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DELETE FROM {{P}}COURSE.
DATA lt_course TYPE STANDARD TABLE OF {{P}}COURSE WITH EMPTY KEY.
lt_course = VALUE #(
( mandt = sy-mandt course_id = 'ABAP101' category = 'IT' title = 'ABAP Basics' capacity = 20 booked = 5 )
( mandt = sy-mandt course_id = 'ABAP102' category = 'IT' title = 'ABAP Advanced' capacity = 10 booked = 10 )
( mandt = sy-mandt course_id = 'ABAP103' category = 'IT' title = 'ABAP Expert' capacity = 12 booked = 15 )
( mandt = sy-mandt course_id = 'SQL101' category = 'DB' title = 'SQL Basics' capacity = 30 booked = 10 )
( mandt = sy-mandt course_id = 'SQL102' category = 'DB' title = 'SQL Tuning' capacity = 8 booked = 8 )
( mandt = sy-mandt course_id = 'HR101' category = 'HR' title = 'HR Basics' capacity = 6 booked = 1 ) ).
INSERT {{P}}COURSE FROM TABLE @lt_course.
out->write( |Courses written: { sy-dbcnt }| ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,68 @@
# 1. Goal
A training provider sells seats in courses. The booking program needs one service that
answers three questions:
- How many seats are free in a course?
- Can we book a given number of seats in a course?
- Which courses of a category still have enough free seats?
# 2. Open questions
None.
# 3. Context
- The database table {{P}}COURSE exists in package $TMP. It has these columns:
MANDT (client), COURSE_ID (CHAR 10, key), CATEGORY (CHAR 10), TITLE (CHAR 40),
CAPACITY (INT4), BOOKED (INT4).
- CAPACITY is the number of seats of the course. BOOKED is the number of seats that
are already sold.
- The class {{P}}SEED_COURSES fills the table with example data. It is active.
# 4. Contract
- Create the class {{P}}COURSE_BOOKING in package $TMP.
- The class is public, final, and has a public constructor without parameters.
- The class has these public types:
- TY_COURSE_ID, type C, length 10.
- TY_CATEGORY, type C, length 10.
- TY_COURSE_INFO, a structure with the components COURSE_ID (type TY_COURSE_ID),
TITLE (type C, length 40) and FREE_SEATS (type I).
- TT_COURSE_INFO, a standard table of TY_COURSE_INFO with an empty key.
- The class has these public methods:
- GET_FREE_SEATS, importing IV_COURSE_ID type TY_COURSE_ID,
returning RV_FREE_SEATS type I.
- IS_BOOKABLE, importing IV_COURSE_ID type TY_COURSE_ID and IV_SEATS type I,
returning RV_BOOKABLE type ABAP_BOOL.
- LIST_BOOKABLE_COURSES, importing IV_CATEGORY type TY_CATEGORY and IV_SEATS type I,
returning RT_COURSES type TT_COURSE_INFO.
- Do not add other public methods.
# 5. Business rules
Free seats
1. Read the course with the course ID.
2. If the table has no course with this ID, the free seats are 0.
3. The free seats are the capacity minus the booked seats.
4. If the capacity minus the booked seats is less than 0, the free seats are 0.
GET_FREE_SEATS returns the free seats of the course.
IS_BOOKABLE
5. If IV_SEATS is less than 1, the result is false.
6. If the table has no course with this ID, the result is false.
7. The result is true if IV_SEATS is less than or equal to the free seats of the
course. Else the result is false.
LIST_BOOKABLE_COURSES
8. Read all courses with the category IV_CATEGORY.
9. Use only the courses whose free seats are greater than or equal to IV_SEATS.
10. Sort the result in ascending order by COURSE_ID.
11. Each result row has the COURSE_ID, the TITLE and the free seats of the course.
12. If no course matches, the result is empty.
# 6. Constraints
- Release target: 8.16.
- Coding standards: Clean ABAP. Method length below 40 statements. No global variables.
No comment that restates the code.
- Out of scope: do not change the table {{P}}COURSE and the class {{P}}SEED_COURSES.
# 7. Acceptance
- The class is active and has no syntax error.
- The hidden tests pass.
- Write your own ABAP Unit tests for the class.

View File

@@ -0,0 +1,58 @@
{
"id": "G0003",
"category": "B",
"object_type": "CLAS",
"difficulty": 2,
"release_target": "v816",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 60,
"max_activations": 15
},
"seed": [
{
"type": "TABL",
"name": "{{P}}COURSE",
"file": "seed/course.tabl.asddls",
"description": "Training course table with capacity and booked seats"
},
{
"type": "CLAS",
"name": "{{P}}SEED_COURSES",
"file": "seed/seed_courses.clas.abap",
"description": "Fills the course table with example data",
"run": true
}
],
"contract": [
{
"type": "CLAS",
"name": "{{P}}COURSE_BOOKING"
}
],
"out_of_scope": [
"{{P}}COURSE",
"{{P}}SEED_COURSES"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T02_HIDDEN",
"file": "hidden/t02_hidden.clas.abap",
"description": "T02 hidden tests"
}
],
"reference": [
{
"type": "CLAS",
"name": "{{P}}COURSE_BOOKING",
"file": "reference/course_booking.clas.abap",
"description": "Course booking service",
"testclasses_file": "reference/course_booking.testclasses.abap"
}
],
"craft_checks": [
"method_length",
"db_operation_in_loop"
]
}

View File

@@ -0,0 +1,14 @@
{
"id": "G0004",
"pool": "eval",
"object_type": "CLAS",
"category": "C",
"attempts": [
{
"stage": "validate",
"oracle": 100.0,
"null": 0
}
],
"accepted": true
}

View File

@@ -0,0 +1,207 @@
CLASS {{p}}stock_age_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CONSTANTS c_key_date TYPE dats VALUE '20240401'.
DATA mo_cut TYPE REF TO {{p}}if_stock_age.
METHODS setup.
METHODS gr_date_for_age
IMPORTING iv_age TYPE i
RETURNING VALUE(rv_date) TYPE dats.
METHODS empty_input FOR TESTING.
METHODS single_batch_bucket_01 FOR TESTING.
METHODS bucket_boundaries FOR TESTING.
METHODS groups_sum_quantity_and_count FOR TESTING.
METHODS result_is_sorted FOR TESTING.
METHODS invalid_rows_are_ignored FOR TESTING.
METHODS negative_and_zero_quantity FOR TESTING.
METHODS total_groups_by_material FOR TESTING.
METHODS total_is_sorted FOR TESTING.
METHODS total_ignores_invalid_rows FOR TESTING.
ENDCLASS.
CLASS {{p}}stock_age_hidden IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}stock_age( ).
ENDMETHOD.
METHOD gr_date_for_age.
rv_date = c_key_date - iv_age.
ENDMETHOD.
METHOD empty_input.
DATA(lt_result) = mo_cut->analyze(
it_batch = VALUE {{p}}if_stock_age=>tt_batch( )
iv_key_date = c_key_date ).
cl_abap_unit_assert=>assert_initial( act = lt_result ).
ENDMETHOD.
METHOD single_batch_bucket_01.
DATA(lt_result) = mo_cut->analyze(
it_batch = VALUE {{p}}if_stock_age=>tt_batch(
( material = 'MAT-1' batch = 'B1' quantity = 10
gr_date = gr_date_for_age( 0 ) ) )
iv_key_date = c_key_date ).
cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 1 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-material
exp = 'MAT-1' ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-bucket
exp = {{p}}if_stock_age=>c_bucket-d0_30 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-quantity exp = 10 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-batch_count exp = 1 ).
ENDMETHOD.
METHOD bucket_boundaries.
DATA(lt_result) = mo_cut->analyze(
it_batch = VALUE {{p}}if_stock_age=>tt_batch(
( material = 'MAT-B' batch = 'B1' quantity = 1 gr_date = gr_date_for_age( 30 ) )
( material = 'MAT-B' batch = 'B2' quantity = 1 gr_date = gr_date_for_age( 31 ) )
( material = 'MAT-B' batch = 'B3' quantity = 1 gr_date = gr_date_for_age( 60 ) )
( material = 'MAT-B' batch = 'B4' quantity = 1 gr_date = gr_date_for_age( 61 ) )
( material = 'MAT-B' batch = 'B5' quantity = 1 gr_date = gr_date_for_age( 90 ) )
( material = 'MAT-B' batch = 'B6' quantity = 1 gr_date = gr_date_for_age( 91 ) ) )
iv_key_date = c_key_date ).
cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 4 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-bucket
exp = {{p}}if_stock_age=>c_bucket-d0_30 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-batch_count exp = 1 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-bucket
exp = {{p}}if_stock_age=>c_bucket-d31_60 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-batch_count exp = 2 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 3 ]-bucket
exp = {{p}}if_stock_age=>c_bucket-d61_90 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 3 ]-batch_count exp = 2 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 4 ]-bucket
exp = {{p}}if_stock_age=>c_bucket-d91_plus ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 4 ]-batch_count exp = 1 ).
ENDMETHOD.
METHOD groups_sum_quantity_and_count.
DATA(lt_result) = mo_cut->analyze(
it_batch = VALUE {{p}}if_stock_age=>tt_batch(
( material = 'MAT-A' batch = 'B1' quantity = 10 gr_date = gr_date_for_age( 1 ) )
( material = 'MAT-A' batch = 'B2' quantity = 5 gr_date = gr_date_for_age( 2 ) )
( material = 'MAT-A' batch = 'B3' quantity = 2 gr_date = gr_date_for_age( 200 ) ) )
iv_key_date = c_key_date ).
cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 2 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-bucket
exp = {{p}}if_stock_age=>c_bucket-d0_30 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-quantity exp = 15 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-batch_count exp = 2 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-bucket
exp = {{p}}if_stock_age=>c_bucket-d91_plus ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-quantity exp = 2 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-batch_count exp = 1 ).
ENDMETHOD.
METHOD result_is_sorted.
DATA(lt_result) = mo_cut->analyze(
it_batch = VALUE {{p}}if_stock_age=>tt_batch(
( material = 'MAT-C' batch = 'B1' quantity = 1 gr_date = gr_date_for_age( 200 ) )
( material = 'MAT-A' batch = 'B2' quantity = 1 gr_date = gr_date_for_age( 40 ) )
( material = 'MAT-B' batch = 'B3' quantity = 1 gr_date = gr_date_for_age( 1 ) )
( material = 'MAT-A' batch = 'B4' quantity = 1 gr_date = gr_date_for_age( 1 ) )
( material = 'MAT-C' batch = 'B5' quantity = 1 gr_date = gr_date_for_age( 1 ) )
( material = 'MAT-B' batch = 'B6' quantity = 1 gr_date = gr_date_for_age( 70 ) ) )
iv_key_date = c_key_date ).
DATA(lt_expected) = VALUE {{p}}if_stock_age=>tt_age_row(
( material = 'MAT-A' bucket = {{p}}if_stock_age=>c_bucket-d0_30 )
( material = 'MAT-A' bucket = {{p}}if_stock_age=>c_bucket-d31_60 )
( material = 'MAT-B' bucket = {{p}}if_stock_age=>c_bucket-d0_30 )
( material = 'MAT-B' bucket = {{p}}if_stock_age=>c_bucket-d61_90 )
( material = 'MAT-C' bucket = {{p}}if_stock_age=>c_bucket-d0_30 )
( material = 'MAT-C' bucket = {{p}}if_stock_age=>c_bucket-d91_plus ) ).
cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 6 ).
DATA lv_index TYPE i.
LOOP AT lt_expected INTO DATA(ls_expected).
lv_index = lv_index + 1.
cl_abap_unit_assert=>assert_equals( act = lt_result[ lv_index ]-material
exp = ls_expected-material ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ lv_index ]-bucket
exp = ls_expected-bucket ).
ENDLOOP.
ENDMETHOD.
METHOD invalid_rows_are_ignored.
DATA(lt_result) = mo_cut->analyze(
it_batch = VALUE {{p}}if_stock_age=>tt_batch(
( material = 'MAT-A' batch = 'B1' quantity = 10 gr_date = gr_date_for_age( 5 ) )
( material = 'MAT-A' batch = 'B2' quantity = 99 gr_date = '00000000' )
( material = 'MAT-A' batch = 'B3' quantity = 99 gr_date = gr_date_for_age( -5 ) ) )
iv_key_date = c_key_date ).
cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 1 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-quantity exp = 10 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-batch_count exp = 1 ).
ENDMETHOD.
METHOD negative_and_zero_quantity.
DATA(lt_result) = mo_cut->analyze(
it_batch = VALUE {{p}}if_stock_age=>tt_batch(
( material = 'MAT-A' batch = 'B1' quantity = 10 gr_date = gr_date_for_age( 5 ) )
( material = 'MAT-A' batch = 'B2' quantity = -4 gr_date = gr_date_for_age( 6 ) )
( material = 'MAT-B' batch = 'B3' quantity = 5 gr_date = gr_date_for_age( 5 ) )
( material = 'MAT-B' batch = 'B4' quantity = -5 gr_date = gr_date_for_age( 6 ) ) )
iv_key_date = c_key_date ).
cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 2 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-material exp = 'MAT-A' ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-quantity exp = 6 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-batch_count exp = 2 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-material exp = 'MAT-B' ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-quantity exp = 0 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-batch_count exp = 2 ).
ENDMETHOD.
METHOD total_groups_by_material.
DATA(lt_result) = mo_cut->total_by_material(
it_batch = VALUE {{p}}if_stock_age=>tt_batch(
( material = 'MAT-A' batch = 'B1' quantity = 10 gr_date = gr_date_for_age( 5 ) )
( material = 'MAT-A' batch = 'B2' quantity = 5 gr_date = gr_date_for_age( 100 ) )
( material = 'MAT-B' batch = 'B3' quantity = 7 gr_date = gr_date_for_age( 5 ) ) )
iv_key_date = c_key_date ).
cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 2 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-material exp = 'MAT-A' ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-quantity exp = 15 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-batch_count exp = 2 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-material exp = 'MAT-B' ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-quantity exp = 7 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-batch_count exp = 1 ).
ENDMETHOD.
METHOD total_is_sorted.
DATA(lt_result) = mo_cut->total_by_material(
it_batch = VALUE {{p}}if_stock_age=>tt_batch(
( material = 'MAT-Z' batch = 'B1' quantity = 1 gr_date = gr_date_for_age( 5 ) )
( material = 'MAT-A' batch = 'B2' quantity = 1 gr_date = gr_date_for_age( 5 ) )
( material = 'MAT-M' batch = 'B3' quantity = 1 gr_date = gr_date_for_age( 5 ) ) )
iv_key_date = c_key_date ).
cl_abap_unit_assert=>assert_equals( act = lines( lt_result ) exp = 3 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-material exp = 'MAT-A' ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-material exp = 'MAT-M' ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 3 ]-material exp = 'MAT-Z' ).
ENDMETHOD.
METHOD total_ignores_invalid_rows.
DATA(lt_result) = mo_cut->total_by_material(
it_batch = VALUE {{p}}if_stock_age=>tt_batch(
( material = 'MAT-A' batch = 'B1' quantity = 99 gr_date = '00000000' )
( material = 'MAT-A' batch = 'B2' quantity = 99 gr_date = gr_date_for_age( -1 ) ) )
iv_key_date = c_key_date ).
cl_abap_unit_assert=>assert_initial( act = lt_result ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,92 @@
CLASS {{p}}stock_age DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_stock_age.
PRIVATE SECTION.
TYPES tt_age_group TYPE HASHED TABLE OF {{p}}if_stock_age=>ty_age_row
WITH UNIQUE KEY material bucket.
TYPES tt_total_group TYPE HASHED TABLE OF {{p}}if_stock_age=>ty_total
WITH UNIQUE KEY material.
METHODS age_in_days
IMPORTING is_batch TYPE {{p}}if_stock_age=>ty_batch
iv_key_date TYPE dats
RETURNING VALUE(rv_age) TYPE i.
METHODS bucket_of
IMPORTING iv_age TYPE i
RETURNING VALUE(rv_bucket) TYPE {{p}}if_stock_age=>ty_bucket.
ENDCLASS.
CLASS {{p}}stock_age IMPLEMENTATION.
METHOD {{p}}if_stock_age~analyze.
DATA lt_group TYPE tt_age_group.
LOOP AT it_batch INTO DATA(ls_batch).
DATA(lv_age) = age_in_days( is_batch = ls_batch iv_key_date = iv_key_date ).
IF lv_age < 0.
CONTINUE.
ENDIF.
DATA(lv_bucket) = bucket_of( lv_age ).
ASSIGN lt_group[ material = ls_batch-material
bucket = lv_bucket ] TO FIELD-SYMBOL(<ls_group>).
IF sy-subrc <> 0.
INSERT VALUE #( material = ls_batch-material bucket = lv_bucket )
INTO TABLE lt_group.
ASSIGN lt_group[ material = ls_batch-material
bucket = lv_bucket ] TO <ls_group>.
ENDIF.
<ls_group>-quantity = <ls_group>-quantity + ls_batch-quantity.
<ls_group>-batch_count = <ls_group>-batch_count + 1.
ENDLOOP.
rt_age = lt_group.
SORT rt_age BY material bucket.
ENDMETHOD.
METHOD {{p}}if_stock_age~total_by_material.
DATA lt_group TYPE tt_total_group.
LOOP AT it_batch INTO DATA(ls_batch).
IF age_in_days( is_batch = ls_batch iv_key_date = iv_key_date ) < 0.
CONTINUE.
ENDIF.
ASSIGN lt_group[ material = ls_batch-material ] TO FIELD-SYMBOL(<ls_group>).
IF sy-subrc <> 0.
INSERT VALUE #( material = ls_batch-material ) INTO TABLE lt_group.
ASSIGN lt_group[ material = ls_batch-material ] TO <ls_group>.
ENDIF.
<ls_group>-quantity = <ls_group>-quantity + ls_batch-quantity.
<ls_group>-batch_count = <ls_group>-batch_count + 1.
ENDLOOP.
rt_total = lt_group.
SORT rt_total BY material.
ENDMETHOD.
METHOD age_in_days.
IF is_batch-gr_date IS INITIAL OR is_batch-gr_date > iv_key_date.
rv_age = -1.
ELSE.
rv_age = iv_key_date - is_batch-gr_date.
ENDIF.
ENDMETHOD.
METHOD bucket_of.
IF iv_age <= 30.
rv_bucket = {{p}}if_stock_age=>c_bucket-d0_30.
ELSEIF iv_age <= 60.
rv_bucket = {{p}}if_stock_age=>c_bucket-d31_60.
ELSEIF iv_age <= 90.
rv_bucket = {{p}}if_stock_age=>c_bucket-d61_90.
ELSE.
rv_bucket = {{p}}if_stock_age=>c_bucket-d91_plus.
ENDIF.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,80 @@
CLASS ltc_stock_age DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CONSTANTS c_key_date TYPE dats VALUE '20240401'.
DATA mo_cut TYPE REF TO {{p}}if_stock_age.
METHODS setup.
METHODS gr_date_for_age
IMPORTING iv_age TYPE i
RETURNING VALUE(rv_date) TYPE dats.
METHODS empty_input FOR TESTING.
METHODS bucket_01 FOR TESTING.
METHODS bucket_04 FOR TESTING.
METHODS invalid_rows FOR TESTING.
METHODS total_by_material FOR TESTING.
ENDCLASS.
CLASS ltc_stock_age IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}stock_age( ).
ENDMETHOD.
METHOD gr_date_for_age.
rv_date = c_key_date - iv_age.
ENDMETHOD.
METHOD empty_input.
cl_abap_unit_assert=>assert_initial(
act = mo_cut->analyze( it_batch = VALUE {{p}}if_stock_age=>tt_batch( )
iv_key_date = c_key_date ) ).
ENDMETHOD.
METHOD bucket_01.
DATA(lt_result) = mo_cut->analyze(
it_batch = VALUE {{p}}if_stock_age=>tt_batch(
( material = 'MAT-1' batch = 'B1' quantity = 10
gr_date = gr_date_for_age( 30 ) ) )
iv_key_date = c_key_date ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-bucket
exp = {{p}}if_stock_age=>c_bucket-d0_30 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-quantity exp = 10 ).
ENDMETHOD.
METHOD bucket_04.
DATA(lt_result) = mo_cut->analyze(
it_batch = VALUE {{p}}if_stock_age=>tt_batch(
( material = 'MAT-1' batch = 'B1' quantity = 10
gr_date = gr_date_for_age( 91 ) ) )
iv_key_date = c_key_date ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-bucket
exp = {{p}}if_stock_age=>c_bucket-d91_plus ).
ENDMETHOD.
METHOD invalid_rows.
DATA(lt_result) = mo_cut->analyze(
it_batch = VALUE {{p}}if_stock_age=>tt_batch(
( material = 'MAT-1' batch = 'B1' quantity = 10 gr_date = '00000000' ) )
iv_key_date = c_key_date ).
cl_abap_unit_assert=>assert_initial( act = lt_result ).
ENDMETHOD.
METHOD total_by_material.
DATA(lt_result) = mo_cut->total_by_material(
it_batch = VALUE {{p}}if_stock_age=>tt_batch(
( material = 'MAT-2' batch = 'B1' quantity = 10 gr_date = gr_date_for_age( 1 ) )
( material = 'MAT-1' batch = 'B2' quantity = 5 gr_date = gr_date_for_age( 100 ) ) )
iv_key_date = c_key_date ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-material exp = 'MAT-1' ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 1 ]-quantity exp = 5 ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-material exp = 'MAT-2' ).
cl_abap_unit_assert=>assert_equals( act = lt_result[ 2 ]-quantity exp = 10 ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,45 @@
INTERFACE {{p}}if_stock_age PUBLIC.
TYPES:
ty_material TYPE c LENGTH 10,
ty_batch_id TYPE c LENGTH 10,
ty_bucket TYPE c LENGTH 2,
ty_quantity TYPE p LENGTH 13 DECIMALS 3,
BEGIN OF ty_batch,
material TYPE ty_material,
batch TYPE ty_batch_id,
quantity TYPE ty_quantity,
gr_date TYPE dats,
END OF ty_batch,
tt_batch TYPE STANDARD TABLE OF ty_batch WITH EMPTY KEY,
BEGIN OF ty_age_row,
material TYPE ty_material,
bucket TYPE ty_bucket,
quantity TYPE ty_quantity,
batch_count TYPE i,
END OF ty_age_row,
tt_age_row TYPE STANDARD TABLE OF ty_age_row WITH EMPTY KEY,
BEGIN OF ty_total,
material TYPE ty_material,
quantity TYPE ty_quantity,
batch_count TYPE i,
END OF ty_total,
tt_total TYPE STANDARD TABLE OF ty_total WITH EMPTY KEY.
CONSTANTS:
BEGIN OF c_bucket,
d0_30 TYPE ty_bucket VALUE '01',
d31_60 TYPE ty_bucket VALUE '02',
d61_90 TYPE ty_bucket VALUE '03',
d91_plus TYPE ty_bucket VALUE '04',
END OF c_bucket.
METHODS analyze
IMPORTING it_batch TYPE tt_batch
iv_key_date TYPE dats
RETURNING VALUE(rt_age) TYPE tt_age_row.
METHODS total_by_material
IMPORTING it_batch TYPE tt_batch
iv_key_date TYPE dats
RETURNING VALUE(rt_total) TYPE tt_total.
ENDINTERFACE.

View File

@@ -0,0 +1,80 @@
# 1. Goal
Analyse the age of stock batches. A warehouse report calls the analysis before it
prints the stock aging report. The report needs the grouped quantity and the number
of batches for each material and age bucket.
# 2. Open questions
None.
# 3. Context
- The interface {{P}}IF_STOCK_AGE exists in package $TMP. It is active.
- The interface defines these types:
- ty_material: character, length 10.
- ty_batch_id: character, length 10.
- ty_bucket: character, length 2.
- ty_quantity: packed number, length 13, 3 decimals.
- ty_batch: structure with the components material, batch, quantity, gr_date.
- tt_batch: table of ty_batch.
- ty_age_row: structure with the components material, bucket, quantity, batch_count.
- tt_age_row: table of ty_age_row.
- ty_total: structure with the components material, quantity, batch_count.
- tt_total: table of ty_total.
- The interface defines the constant structure C_BUCKET with the components
D0_30 = '01', D31_60 = '02', D61_90 = '03', D91_PLUS = '04'.
- The interface defines two methods:
- analyze: importing it_batch type tt_batch and iv_key_date type dats,
returning rt_age type tt_age_row.
- total_by_material: importing it_batch type tt_batch and iv_key_date type dats,
returning rt_total type tt_total.
# 4. Contract
- Create the class {{P}}STOCK_AGE in package $TMP.
- The class is public, final, and has a public constructor without parameters.
- The class implements {{P}}IF_STOCK_AGE.
- Do not add other public methods.
# 5. Business rules
Both methods use the same validity rules and the same age buckets.
Validity
1. A row of IT_BATCH is valid when its goods receipt date is not initial and is not
after IV_KEY_DATE.
2. A row that is not valid is ignored. It must not change any result.
Age and bucket
3. The age of a batch is the number of days between its goods receipt date and
IV_KEY_DATE.
4. The bucket of a batch is:
- age 0 to 30 days: bucket 01 (C_BUCKET-D0_30)
- age 31 to 60 days: bucket 02 (C_BUCKET-D31_60)
- age 61 to 90 days: bucket 03 (C_BUCKET-D61_90)
- age more than 90 days: bucket 04 (C_BUCKET-D91_PLUS)
Method ANALYZE
5. Group the valid rows by material and bucket.
6. For each group, sum the quantity and count the valid rows.
7. Return one row per group. Sort the result by material ascending and then by
bucket ascending.
8. A group is in the result as soon as one valid row belongs to it, also when the
summed quantity is zero.
9. If no valid row exists, return an empty table.
Method TOTAL_BY_MATERIAL
10. Group the valid rows by material only.
11. For each group, sum the quantity and count the valid rows.
12. Return one row per material. Sort the result by material ascending.
13. If no valid row exists, return an empty table.
Quantity
14. The quantity of a batch can be negative. Add the quantities as they are.
# 6. Constraints
- Release target: SAP_BASIS 8.16 (ABAP Platform 2025).
- Coding standards: Clean ABAP. Method length below 40 statements. No global
variables. No comment that restates the code. Pass large parameters by reference.
- Out of scope: do not change {{P}}IF_STOCK_AGE.
# 7. Acceptance
- The class is active and has no syntax error.
- The hidden tests pass.
- Write your own ABAP Unit tests for the class.

View File

@@ -0,0 +1,53 @@
{
"id": "G0004",
"category": "C",
"object_type": "CLAS",
"difficulty": 2,
"release_target": "v816",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 60,
"max_activations": 12
},
"seed": [
{
"type": "INTF",
"name": "{{P}}IF_STOCK_AGE",
"file": "seed/if_stock_age.intf.abap",
"description": "Stock age contract: row types, bucket constants and the two analysis methods"
}
],
"contract": [
{
"type": "CLAS",
"name": "{{P}}STOCK_AGE",
"implements": "{{P}}IF_STOCK_AGE"
}
],
"out_of_scope": [
"{{P}}IF_STOCK_AGE"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}STOCK_AGE_HIDDEN",
"file": "hidden/stock_age_hidden.clas.abap",
"description": "Hidden tests for the stock age analysis"
}
],
"reference": [
{
"type": "CLAS",
"name": "{{P}}STOCK_AGE",
"file": "reference/stock_age.clas.abap",
"description": "Stock age analysis",
"testclasses_file": "reference/stock_age.testclasses.abap"
}
],
"craft_checks": [
"table_kind",
"access_path",
"method_length",
"no_nested_loops"
]
}

View File

@@ -0,0 +1,29 @@
{
"id": "G0005",
"pool": "eval",
"object_type": "CLAS",
"category": "D",
"attempts": [
{
"stage": "validate",
"oracle": null,
"null": null
},
{
"stage": "validate",
"oracle": 0,
"null": 0
},
{
"stage": "validate",
"oracle": 0,
"null": 0
},
{
"stage": "validate",
"oracle": 0,
"null": 0
}
],
"accepted": false
}

View File

@@ -0,0 +1,158 @@
CLASS {{p}}t02_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
TYPES ty_cost TYPE p LENGTH 15 DECIMALS 2.
TYPES ty_currency TYPE c LENGTH 5.
TYPES ty_zone_code TYPE c LENGTH 4.
TYPES ty_weight TYPE p LENGTH 15 DECIMALS 3.
DATA mo_cut TYPE REF TO {{p}}freight_calc.
METHODS setup.
METHODS calculate_result
IMPORTING iv_zone_code TYPE ty_zone_code
iv_weight_kg TYPE ty_weight
iv_express TYPE abap_bool DEFAULT abap_false
RETURNING VALUE(rs_result) TYPE {{p}}freight_calc=>ty_result.
METHODS assert_cost
IMPORTING iv_cost TYPE ty_cost
iv_currency TYPE ty_currency
is_result TYPE {{p}}freight_calc=>ty_result.
METHODS assert_weight
IMPORTING iv_expected TYPE ty_weight
iv_actual TYPE ty_weight
iv_msg TYPE string.
METHODS standard_cost FOR TESTING.
METHODS express_surcharge FOR TESTING.
METHODS express_no_early_rounding FOR TESTING.
METHODS minimum_charge FOR TESTING.
METHODS rounding FOR TESTING.
METHODS zone_code_normalized FOR TESTING.
METHODS weight_at_limit FOR TESTING.
METHODS unknown_zone FOR TESTING.
METHODS invalid_weight FOR TESTING.
METHODS weight_limit FOR TESTING.
METHODS weight_check_before_zone_lookup FOR TESTING.
ENDCLASS.
CLASS {{p}}t02_hidden IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}freight_calc( ).
ENDMETHOD.
METHOD calculate_result.
TRY.
rs_result = mo_cut->calculate( iv_zone_code = iv_zone_code
iv_weight_kg = iv_weight_kg
iv_express = iv_express ).
CATCH {{p}}cx_freight_error INTO DATA(lo_error).
cl_abap_unit_assert=>fail( msg = lo_error->get_reason_text( ) ).
ENDTRY.
ENDMETHOD.
METHOD assert_cost.
cl_abap_unit_assert=>assert_equals( exp = iv_cost act = is_result-cost msg = 'cost' ).
cl_abap_unit_assert=>assert_equals( exp = iv_currency act = is_result-currency msg = 'currency' ).
ENDMETHOD.
METHOD assert_weight.
cl_abap_unit_assert=>assert_equals( exp = iv_expected act = iv_actual msg = iv_msg ).
ENDMETHOD.
METHOD standard_cost.
DATA(ls_result) = calculate_result( iv_zone_code = 'DE01' iv_weight_kg = '10.000' ).
assert_cost( iv_cost = '17.00' iv_currency = 'EUR' is_result = ls_result ).
ENDMETHOD.
METHOD express_surcharge.
DATA(ls_result) = calculate_result( iv_zone_code = 'DE01'
iv_weight_kg = '10.000'
iv_express = abap_true ).
assert_cost( iv_cost = '25.50' iv_currency = 'EUR' is_result = ls_result ).
ENDMETHOD.
METHOD express_no_early_rounding.
DATA(ls_result) = calculate_result( iv_zone_code = 'DE01'
iv_weight_kg = '10.004'
iv_express = abap_true ).
assert_cost( iv_cost = '25.51' iv_currency = 'EUR' is_result = ls_result ).
ENDMETHOD.
METHOD minimum_charge.
DATA(ls_result) = calculate_result( iv_zone_code = 'DE01' iv_weight_kg = '0.500' ).
assert_cost( iv_cost = '6.00' iv_currency = 'EUR' is_result = ls_result ).
ENDMETHOD.
METHOD rounding.
DATA(ls_result) = calculate_result( iv_zone_code = 'FR01' iv_weight_kg = '1.234' ).
assert_cost( iv_cost = '12.09' iv_currency = 'EUR' is_result = ls_result ).
ENDMETHOD.
METHOD zone_code_normalized.
DATA(ls_result) = calculate_result( iv_zone_code = 'de01' iv_weight_kg = '10.000' ).
assert_cost( iv_cost = '17.00' iv_currency = 'EUR' is_result = ls_result ).
ENDMETHOD.
METHOD weight_at_limit.
DATA(ls_result) = calculate_result( iv_zone_code = 'CN01' iv_weight_kg = '20.000' ).
assert_cost( iv_cost = '103.00' iv_currency = 'CNY' is_result = ls_result ).
ENDMETHOD.
METHOD unknown_zone.
TRY.
mo_cut->calculate( iv_zone_code = 'xx99' iv_weight_kg = '1.000' ).
cl_abap_unit_assert=>fail( msg = 'CX_UNKNOWN_ZONE expected' ).
CATCH {{p}}cx_unknown_zone INTO DATA(lo_unknown).
cl_abap_unit_assert=>assert_equals( exp = 'XX99' act = lo_unknown->mv_zone_code ).
cl_abap_unit_assert=>assert_equals( exp = `Zone XX99 is not known`
act = lo_unknown->get_reason_text( ) ).
CATCH {{p}}cx_freight_error INTO DATA(lo_other).
cl_abap_unit_assert=>fail( msg = |Wrong exception class: { lo_other->get_reason_text( ) }| ).
ENDTRY.
ENDMETHOD.
METHOD invalid_weight.
TRY.
mo_cut->calculate( iv_zone_code = 'DE01' iv_weight_kg = '0.000' ).
cl_abap_unit_assert=>fail( msg = 'CX_INVALID_WEIGHT expected' ).
CATCH {{p}}cx_invalid_weight INTO DATA(lo_invalid).
assert_weight( iv_expected = '0.000' iv_actual = lo_invalid->mv_weight_kg iv_msg = 'weight' ).
cl_abap_unit_assert=>assert_equals( exp = 'DE01' act = lo_invalid->mv_zone_code ).
cl_abap_unit_assert=>assert_equals( exp = `Weight must be greater than zero`
act = lo_invalid->get_reason_text( ) ).
CATCH {{p}}cx_freight_error INTO DATA(lo_other).
cl_abap_unit_assert=>fail( msg = |Wrong exception class: { lo_other->get_reason_text( ) }| ).
ENDTRY.
ENDMETHOD.
METHOD weight_limit.
TRY.
mo_cut->calculate( iv_zone_code = 'CN01' iv_weight_kg = '20.001' ).
cl_abap_unit_assert=>fail( msg = 'CX_WEIGHT_LIMIT expected' ).
CATCH {{p}}cx_weight_limit INTO DATA(lo_limit).
assert_weight( iv_expected = '20.001' iv_actual = lo_limit->mv_weight_kg iv_msg = 'weight' ).
cl_abap_unit_assert=>assert_equals( exp = 20 act = lo_limit->mv_max_weight_kg msg = 'max weight' ).
cl_abap_unit_assert=>assert_equals( exp = 'CN01' act = lo_limit->mv_zone_code ).
cl_abap_unit_assert=>assert_equals( exp = `Weight exceeds the zone limit`
act = lo_limit->get_reason_text( ) ).
CATCH {{p}}cx_freight_error INTO DATA(lo_other).
cl_abap_unit_assert=>fail( msg = |Wrong exception class: { lo_other->get_reason_text( ) }| ).
ENDTRY.
ENDMETHOD.
METHOD weight_check_before_zone_lookup.
TRY.
mo_cut->calculate( iv_zone_code = 'ZZZZ' iv_weight_kg = '0.000' ).
cl_abap_unit_assert=>fail( msg = 'CX_INVALID_WEIGHT expected' ).
CATCH {{p}}cx_invalid_weight INTO DATA(lo_invalid).
cl_abap_unit_assert=>assert_equals( exp = 'ZZZZ' act = lo_invalid->mv_zone_code ).
CATCH {{p}}cx_freight_error INTO DATA(lo_other).
cl_abap_unit_assert=>fail( msg = |Wrong exception class: { lo_other->get_reason_text( ) }| ).
ENDTRY.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,11 @@
CLASS {{p}}cx_freight_error DEFINITION PUBLIC INHERITING FROM cx_static_check ABSTRACT.
PUBLIC SECTION.
DATA mv_zone_code TYPE c LENGTH 4.
METHODS get_reason_text ABSTRACT
RETURNING VALUE(rv_text) TYPE string.
ENDCLASS.
CLASS {{p}}cx_freight_error IMPLEMENTATION.
ENDCLASS.

View File

@@ -0,0 +1,13 @@
CLASS {{p}}cx_invalid_weight DEFINITION PUBLIC INHERITING FROM {{p}}cx_freight_error FINAL CREATE PUBLIC.
PUBLIC SECTION.
DATA mv_weight_kg TYPE p LENGTH 15 DECIMALS 3.
METHODS get_reason_text REDEFINITION.
ENDCLASS.
CLASS {{p}}cx_invalid_weight IMPLEMENTATION.
METHOD get_reason_text.
rv_text = 'Weight must be greater than zero'.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,11 @@
CLASS {{p}}cx_unknown_zone DEFINITION PUBLIC INHERITING FROM {{p}}cx_freight_error FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS get_reason_text REDEFINITION.
ENDCLASS.
CLASS {{p}}cx_unknown_zone IMPLEMENTATION.
METHOD get_reason_text.
rv_text = |Zone { condense( mv_zone_code ) } is not known|.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,14 @@
CLASS {{p}}cx_weight_limit DEFINITION PUBLIC INHERITING FROM {{p}}cx_freight_error FINAL CREATE PUBLIC.
PUBLIC SECTION.
DATA mv_weight_kg TYPE p LENGTH 15 DECIMALS 3.
DATA mv_max_weight_kg TYPE i.
METHODS get_reason_text REDEFINITION.
ENDCLASS.
CLASS {{p}}cx_weight_limit IMPLEMENTATION.
METHOD get_reason_text.
rv_text = 'Weight exceeds the zone limit'.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,73 @@
CLASS {{p}}freight_calc DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES ty_cost TYPE p LENGTH 15 DECIMALS 2.
TYPES ty_weight TYPE p LENGTH 15 DECIMALS 3.
TYPES: BEGIN OF ty_result,
cost TYPE ty_cost,
currency TYPE c LENGTH 5,
END OF ty_result.
METHODS calculate
IMPORTING iv_zone_code TYPE c LENGTH 4
iv_weight_kg TYPE ty_weight
iv_express TYPE abap_bool DEFAULT abap_false
RETURNING VALUE(rs_result) TYPE ty_result
RAISING {{p}}cx_freight_error.
PRIVATE SECTION.
CONSTANTS c_express_factor TYPE p LENGTH 5 DECIMALS 2 VALUE '1.50'.
CONSTANTS c_minimum_charge TYPE ty_cost VALUE '6.00'.
METHODS find_zone
IMPORTING iv_zone_code TYPE c LENGTH 4
RETURNING VALUE(rs_zone) TYPE {{p}}zone_master=>ty_zone
RAISING {{p}}cx_freight_error.
ENDCLASS.
CLASS {{p}}freight_calc IMPLEMENTATION.
METHOD calculate.
DATA lv_zone_code TYPE c LENGTH 4.
lv_zone_code = to_upper( iv_zone_code ).
IF iv_weight_kg <= 0.
DATA(lo_invalid) = NEW {{p}}cx_invalid_weight( ).
lo_invalid->mv_zone_code = lv_zone_code.
lo_invalid->mv_weight_kg = iv_weight_kg.
RAISE EXCEPTION lo_invalid.
ENDIF.
DATA(ls_zone) = find_zone( lv_zone_code ).
IF iv_weight_kg > ls_zone-max_weight_kg.
DATA(lo_limit) = NEW {{p}}cx_weight_limit( ).
lo_limit->mv_zone_code = lv_zone_code.
lo_limit->mv_weight_kg = iv_weight_kg.
lo_limit->mv_max_weight_kg = ls_zone-max_weight_kg.
RAISE EXCEPTION lo_limit.
ENDIF.
DATA lv_cost TYPE p LENGTH 15 DECIMALS 5.
lv_cost = ls_zone-base_fee + iv_weight_kg * ls_zone-price_per_kg.
IF iv_express = abap_true.
lv_cost = lv_cost * c_express_factor.
ENDIF.
lv_cost = round( val = lv_cost dec = 2 ).
IF lv_cost < c_minimum_charge.
lv_cost = c_minimum_charge.
ENDIF.
rs_result-cost = lv_cost.
rs_result-currency = ls_zone-currency.
ENDMETHOD.
METHOD find_zone.
DATA(lt_zone) = {{p}}zone_master=>get_all( ).
READ TABLE lt_zone INTO rs_zone WITH KEY zone_code = iv_zone_code.
IF sy-subrc <> 0.
DATA(lo_unknown) = NEW {{p}}cx_unknown_zone( ).
lo_unknown->mv_zone_code = iv_zone_code.
RAISE EXCEPTION lo_unknown.
ENDIF.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,98 @@
CLASS ltc_freight_calc DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
TYPES ty_cost TYPE p LENGTH 15 DECIMALS 2.
TYPES ty_currency TYPE c LENGTH 5.
TYPES ty_zone_code TYPE c LENGTH 4.
TYPES ty_weight TYPE p LENGTH 15 DECIMALS 3.
DATA mo_cut TYPE REF TO {{p}}freight_calc.
METHODS setup.
METHODS assert_cost
IMPORTING iv_cost TYPE ty_cost
iv_currency TYPE ty_currency
is_result TYPE {{p}}freight_calc=>ty_result.
METHODS calculate_result
IMPORTING iv_zone_code TYPE ty_zone_code
iv_weight_kg TYPE ty_weight
iv_express TYPE abap_bool DEFAULT abap_false
RETURNING VALUE(rs_result) TYPE {{p}}freight_calc=>ty_result.
METHODS standard_cost FOR TESTING.
METHODS express_cost FOR TESTING.
METHODS minimum_charge FOR TESTING.
METHODS unknown_zone FOR TESTING.
METHODS invalid_weight FOR TESTING.
METHODS weight_limit FOR TESTING.
ENDCLASS.
CLASS ltc_freight_calc IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}freight_calc( ).
ENDMETHOD.
METHOD assert_cost.
cl_abap_unit_assert=>assert_equals( exp = iv_cost act = is_result-cost ).
cl_abap_unit_assert=>assert_equals( exp = iv_currency act = is_result-currency ).
ENDMETHOD.
METHOD calculate_result.
TRY.
rs_result = mo_cut->calculate( iv_zone_code = iv_zone_code
iv_weight_kg = iv_weight_kg
iv_express = iv_express ).
CATCH {{p}}cx_freight_error INTO DATA(lo_error).
cl_abap_unit_assert=>fail( msg = lo_error->get_reason_text( ) ).
ENDTRY.
ENDMETHOD.
METHOD standard_cost.
DATA(ls_result) = calculate_result( iv_zone_code = 'DE01' iv_weight_kg = '10.000' ).
assert_cost( iv_cost = '17.00' iv_currency = 'EUR' is_result = ls_result ).
ENDMETHOD.
METHOD express_cost.
DATA(ls_result) = calculate_result( iv_zone_code = 'DE01'
iv_weight_kg = '10.000'
iv_express = abap_true ).
assert_cost( iv_cost = '25.50' iv_currency = 'EUR' is_result = ls_result ).
ENDMETHOD.
METHOD minimum_charge.
DATA(ls_result) = calculate_result( iv_zone_code = 'DE01' iv_weight_kg = '0.500' ).
assert_cost( iv_cost = '6.00' iv_currency = 'EUR' is_result = ls_result ).
ENDMETHOD.
METHOD unknown_zone.
TRY.
mo_cut->calculate( iv_zone_code = 'XX99' iv_weight_kg = '1.000' ).
cl_abap_unit_assert=>fail( msg = 'CX_UNKNOWN_ZONE expected' ).
CATCH {{p}}cx_unknown_zone INTO DATA(lo_unknown).
cl_abap_unit_assert=>assert_equals( exp = 'XX99' act = lo_unknown->mv_zone_code ).
CATCH {{p}}cx_freight_error INTO DATA(lo_other).
cl_abap_unit_assert=>fail( msg = |Wrong exception class: { lo_other->get_reason_text( ) }| ).
ENDTRY.
ENDMETHOD.
METHOD invalid_weight.
TRY.
mo_cut->calculate( iv_zone_code = 'DE01' iv_weight_kg = '0.000' ).
cl_abap_unit_assert=>fail( msg = 'CX_INVALID_WEIGHT expected' ).
CATCH {{p}}cx_invalid_weight INTO DATA(lo_invalid).
cl_abap_unit_assert=>assert_equals( exp = 'DE01' act = lo_invalid->mv_zone_code ).
CATCH {{p}}cx_freight_error INTO DATA(lo_other).
cl_abap_unit_assert=>fail( msg = |Wrong exception class: { lo_other->get_reason_text( ) }| ).
ENDTRY.
ENDMETHOD.
METHOD weight_limit.
TRY.
mo_cut->calculate( iv_zone_code = 'CN01' iv_weight_kg = '20.001' ).
cl_abap_unit_assert=>fail( msg = 'CX_WEIGHT_LIMIT expected' ).
CATCH {{p}}cx_weight_limit INTO DATA(lo_limit).
cl_abap_unit_assert=>assert_equals( exp = 'CN01' act = lo_limit->mv_zone_code ).
CATCH {{p}}cx_freight_error INTO DATA(lo_other).
cl_abap_unit_assert=>fail( msg = |Wrong exception class: { lo_other->get_reason_text( ) }| ).
ENDTRY.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,18 @@
CLASS {{p}}seed_data DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS {{p}}seed_data IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DELETE FROM {{p}}zones.
INSERT {{p}}zones FROM TABLE @( VALUE #(
( zone_code = 'DE01' country = 'DE' base_fee = '5.00' price_per_kg = '1.20' max_weight_kg = 31 currency = 'EUR' )
( zone_code = 'DE02' country = 'DE' base_fee = '8.00' price_per_kg = '1.80' max_weight_kg = 31 currency = 'EUR' )
( zone_code = 'FR01' country = 'FR' base_fee = '9.50' price_per_kg = '2.10' max_weight_kg = 30 currency = 'EUR' )
( zone_code = 'US01' country = 'US' base_fee = '14.00' price_per_kg = '3.40' max_weight_kg = 25 currency = 'USD' )
( zone_code = 'CN01' country = 'CN' base_fee = '18.00' price_per_kg = '4.25' max_weight_kg = 20 currency = 'CNY' ) ) ).
out->write( |Zones seeded| ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,27 @@
CLASS {{p}}zone_master DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES: BEGIN OF ty_zone,
zone_code TYPE c LENGTH 4,
country TYPE c LENGTH 2,
base_fee TYPE p LENGTH 15 DECIMALS 2,
price_per_kg TYPE p LENGTH 15 DECIMALS 2,
max_weight_kg TYPE i,
currency TYPE c LENGTH 5,
END OF ty_zone,
tt_zone TYPE SORTED TABLE OF ty_zone WITH UNIQUE KEY zone_code.
CLASS-METHODS get_all
RETURNING VALUE(rt_zone) TYPE tt_zone.
ENDCLASS.
CLASS {{p}}zone_master IMPLEMENTATION.
METHOD get_all.
rt_zone = VALUE #(
( zone_code = 'DE01' country = 'DE' base_fee = '5.00' price_per_kg = '1.20' max_weight_kg = 31 currency = 'EUR' )
( zone_code = 'DE02' country = 'DE' base_fee = '8.00' price_per_kg = '1.80' max_weight_kg = 31 currency = 'EUR' )
( zone_code = 'FR01' country = 'FR' base_fee = '9.50' price_per_kg = '2.10' max_weight_kg = 30 currency = 'EUR' )
( zone_code = 'US01' country = 'US' base_fee = '14.00' price_per_kg = '3.40' max_weight_kg = 25 currency = 'USD' )
( zone_code = 'CN01' country = 'CN' base_fee = '18.00' price_per_kg = '4.25' max_weight_kg = 20 currency = 'CNY' ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,10 @@
@EndUserText.label : 'Zone tariff'
define table {{p}}zones {
key mandt : abap.clnt not null;
key zone_code : abap.char(4) not null;
country : abap.char(2) not null;
base_fee : abap.dec(15,2) not null;
price_per_kg : abap.dec(15,2) not null;
max_weight_kg : abap.int4 not null;
currency : abap.char(5) not null;
}

View File

@@ -0,0 +1,103 @@
# 1. Goal
A parcel service calculates the freight cost of a parcel from a zone code and a
weight. Each rejected input must be reported as its own class-based exception, so
that the calling program can react to the exact reason.
# 2. Open questions
None.
# 3. Context
- The class {{P}}ZONE_MASTER exists in package $TMP and is active. It provides the
zone master data. Do not change it.
- {{P}}ZONE_MASTER has these public components:
- TYPES TY_ZONE with the components ZONE_CODE (c LENGTH 4), COUNTRY (c LENGTH 2),
BASE_FEE (p LENGTH 15 DECIMALS 2), PRICE_PER_KG (p LENGTH 15 DECIMALS 2),
MAX_WEIGHT_KG (i), CURRENCY (c LENGTH 5).
- TYPES TT_ZONE, a sorted table of TY_ZONE with the unique key ZONE_CODE.
- CLASS-METHOD GET_ALL returning the table TT_ZONE.
- The zone master data contains these entries:
| ZONE_CODE | COUNTRY | BASE_FEE | PRICE_PER_KG | MAX_WEIGHT_KG | CURRENCY |
| --------- | ------- | -------- | ------------ | ------------- | -------- |
| DE01 | DE | 5.00 | 1.20 | 31 | EUR |
| DE02 | DE | 8.00 | 1.80 | 31 | EUR |
| FR01 | FR | 9.50 | 2.10 | 30 | EUR |
| US01 | US | 14.00 | 3.40 | 25 | USD |
| CN01 | CN | 18.00 | 4.25 | 20 | CNY |
# 4. Contract
Create these classes in package $TMP.
{{P}}CX_FREIGHT_ERROR
- public, abstract, inherits from CX_STATIC_CHECK.
- public attribute MV_ZONE_CODE type c LENGTH 4.
- public method GET_REASON_TEXT returning VALUE(rv_text) type string.
{{P}}CX_UNKNOWN_ZONE
- public, inherits from {{P}}CX_FREIGHT_ERROR.
{{P}}CX_INVALID_WEIGHT
- public, inherits from {{P}}CX_FREIGHT_ERROR.
- public attribute MV_WEIGHT_KG type p LENGTH 15 DECIMALS 3.
{{P}}CX_WEIGHT_LIMIT
- public, inherits from {{P}}CX_FREIGHT_ERROR.
- public attribute MV_WEIGHT_KG type p LENGTH 15 DECIMALS 3.
- public attribute MV_MAX_WEIGHT_KG type i.
GET_REASON_TEXT returns the reason text of the exception. Each of the three
concrete exception classes returns its own text, see the business rules.
{{P}}FREIGHT_CALC
- public, final, create public, constructor without parameters.
- public type TY_COST (p LENGTH 15 DECIMALS 2).
- public type TY_WEIGHT (p LENGTH 15 DECIMALS 3).
- public type TY_RESULT with the components COST (type TY_COST) and CURRENCY
(c LENGTH 5).
- public method CALCULATE:
IMPORTING iv_zone_code TYPE c LENGTH 4
iv_weight_kg TYPE ty_weight
iv_express TYPE abap_bool DEFAULT abap_false
RETURNING VALUE(rs_result) TYPE ty_result
RAISING {{p}}cx_freight_error
- no other public methods.
Use the named types of this contract in the method interfaces. Do not write the
additions LENGTH or DECIMALS in a method interface.
# 5. Business rules
CALCULATE applies these rules in this sequence.
1. Convert the zone code to upper case. Use this value in all further steps and in
the attribute MV_ZONE_CODE of every exception.
2. If the weight is less than or equal to zero, raise {{P}}CX_INVALID_WEIGHT. Set
MV_ZONE_CODE to the zone code and MV_WEIGHT_KG to the given weight.
3. Read the zone master data with {{P}}ZONE_MASTER=>GET_ALL and find the entry
whose ZONE_CODE equals the zone code. If there is no such entry, raise
{{P}}CX_UNKNOWN_ZONE and set MV_ZONE_CODE to the zone code.
4. If the weight is greater than MAX_WEIGHT_KG, raise {{P}}CX_WEIGHT_LIMIT. Set
MV_ZONE_CODE to the zone code, MV_WEIGHT_KG to the given weight and
MV_MAX_WEIGHT_KG to MAX_WEIGHT_KG.
5. Calculate the cost as BASE_FEE + weight x PRICE_PER_KG.
6. If IV_EXPRESS is ABAP_TRUE, multiply the cost by 1.5.
7. Keep the full precision of the cost until this step. Do not round the cost
before this step. Round the cost to 2 decimal places with commercial rounding.
8. If the rounded cost is less than 6.00, use 6.00.
9. Return the cost in COST and the CURRENCY of the zone in CURRENCY.
GET_REASON_TEXT returns:
- in {{P}}CX_UNKNOWN_ZONE: 'Zone ', then the zone code, then ' is not known'.
Example: 'Zone XX99 is not known'.
- in {{P}}CX_INVALID_WEIGHT: 'Weight must be greater than zero'.
- in {{P}}CX_WEIGHT_LIMIT: 'Weight exceeds the zone limit'.
# 6. Constraints
- Release target: SAP_BASIS 816 (ABAP Platform 2025).
- Coding standards: Clean ABAP. Methods below 40 statements. No global variables.
- Out of scope: do not change {{P}}ZONE_MASTER.
# 7. Acceptance
- All classes are active and have no syntax error.
- The hidden tests pass.
- Write your own ABAP Unit tests for {{P}}FREIGHT_CALC.

View File

@@ -0,0 +1,90 @@
{
"id": "G0005",
"category": "D",
"object_type": "CLAS",
"difficulty": 2,
"release_target": "v816",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 60,
"max_activations": 20
},
"seed": [
{
"type": "CLAS",
"name": "{{P}}ZONE_MASTER",
"file": "seed/zone_master.clas.abap",
"description": "Zone master data provider"
}
],
"contract": [
{
"type": "CLAS",
"name": "{{P}}CX_FREIGHT_ERROR"
},
{
"type": "CLAS",
"name": "{{P}}CX_UNKNOWN_ZONE"
},
{
"type": "CLAS",
"name": "{{P}}CX_INVALID_WEIGHT"
},
{
"type": "CLAS",
"name": "{{P}}CX_WEIGHT_LIMIT"
},
{
"type": "CLAS",
"name": "{{P}}FREIGHT_CALC"
}
],
"out_of_scope": [
"{{P}}ZONE_MASTER"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T02_HIDDEN",
"file": "hidden/t02_hidden.clas.abap",
"description": "T02 hidden tests"
}
],
"reference": [
{
"type": "CLAS",
"name": "{{P}}CX_FREIGHT_ERROR",
"file": "reference/cx_freight_error.clas.abap",
"description": "Abstract base exception"
},
{
"type": "CLAS",
"name": "{{P}}CX_UNKNOWN_ZONE",
"file": "reference/cx_unknown_zone.clas.abap",
"description": "Unknown zone exception"
},
{
"type": "CLAS",
"name": "{{P}}CX_INVALID_WEIGHT",
"file": "reference/cx_invalid_weight.clas.abap",
"description": "Invalid weight exception"
},
{
"type": "CLAS",
"name": "{{P}}CX_WEIGHT_LIMIT",
"file": "reference/cx_weight_limit.clas.abap",
"description": "Weight limit exception"
},
{
"type": "CLAS",
"name": "{{P}}FREIGHT_CALC",
"file": "reference/freight_calc.clas.abap",
"description": "Freight cost calculator",
"testclasses_file": "reference/freight_calc.testclasses.abap"
}
],
"craft_checks": [
"method_length",
"exception_handling"
]
}

View File

@@ -0,0 +1,30 @@
{
"id": "G0006",
"pool": "eval",
"object_type": "CLAS",
"category": "E",
"attempts": [
{
"stage": "validate",
"oracle": null,
"null": null
},
{
"stage": "validate",
"oracle": null,
"null": null
},
{
"stage": "bundle",
"errors": [
"invalid JSON: Expecting value: line 1 column 1 (char 0)"
]
},
{
"stage": "validate",
"oracle": null,
"null": null
}
],
"accepted": false
}

View File

@@ -0,0 +1,154 @@
CLASS {{p}}t02_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}freight_calculator.
METHODS setup.
METHODS assert_calculation
IMPORTING iv_weight_kg TYPE p LENGTH 8 DECIMALS 2
iv_zone TYPE string
iv_express TYPE abap_bool DEFAULT abap_false
is_expected TYPE {{p}}freight_calculator=>ty_result.
METHODS standard_parcel FOR TESTING.
METHODS express_parcel FOR TESTING.
METHODS zone_normalization FOR TESTING.
METHODS rounding_half_up FOR TESTING.
METHODS zero_weight FOR TESTING.
METHODS negative_weight FOR TESTING.
METHODS unknown_zone FOR TESTING.
METHODS weight_error_wins FOR TESTING.
METHODS supported_zones FOR TESTING.
METHODS unsupported_zones FOR TESTING.
METHODS all_rates FOR TESTING.
ENDCLASS.
CLASS {{p}}t02_hidden IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}freight_calculator( ).
ENDMETHOD.
METHOD assert_calculation.
cl_abap_unit_assert=>assert_equals(
exp = is_expected
act = mo_cut->calculate( iv_weight_kg = iv_weight_kg
iv_zone = iv_zone
iv_express = iv_express )
msg = |zone { iv_zone }| ).
ENDMETHOD.
METHOD standard_parcel.
assert_calculation(
iv_weight_kg = '10.00'
iv_zone = `DE`
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-ok
base = '45.00'
surcharge = '0.00'
total = '45.00' ) ).
ENDMETHOD.
METHOD express_parcel.
assert_calculation(
iv_weight_kg = '10.00'
iv_zone = `DE`
iv_express = abap_true
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-ok
base = '45.00'
surcharge = '11.25'
total = '56.25' ) ).
ENDMETHOD.
METHOD zone_normalization.
assert_calculation(
iv_weight_kg = '5.00'
iv_zone = ` ch `
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-ok
base = '39.00'
total = '39.00' ) ).
assert_calculation(
iv_weight_kg = '5.00'
iv_zone = `de`
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-ok
base = '22.50'
total = '22.50' ) ).
ENDMETHOD.
METHOD rounding_half_up.
assert_calculation(
iv_weight_kg = '3.33'
iv_zone = `DE`
iv_express = abap_true
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-ok
base = '14.99'
surcharge = '3.75'
total = '18.74' ) ).
ENDMETHOD.
METHOD zero_weight.
assert_calculation(
iv_weight_kg = '0.00'
iv_zone = `DE`
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-weight ) ).
ENDMETHOD.
METHOD negative_weight.
assert_calculation(
iv_weight_kg = '-2.50'
iv_zone = `DE`
iv_express = abap_true
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-weight ) ).
ENDMETHOD.
METHOD unknown_zone.
assert_calculation(
iv_weight_kg = '5.00'
iv_zone = `XX`
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-zone ) ).
assert_calculation(
iv_weight_kg = '5.00'
iv_zone = `DEU`
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-zone ) ).
ENDMETHOD.
METHOD weight_error_wins.
assert_calculation(
iv_weight_kg = '0.00'
iv_zone = `XX`
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-weight ) ).
ENDMETHOD.
METHOD supported_zones.
cl_abap_unit_assert=>assert_equals( exp = abap_true act = mo_cut->is_zone_supported( `de` ) msg = `de` ).
cl_abap_unit_assert=>assert_equals( exp = abap_true act = mo_cut->is_zone_supported( ` DE ` ) msg = ` DE ` ).
cl_abap_unit_assert=>assert_equals( exp = abap_true act = mo_cut->is_zone_supported( `at` ) msg = `at` ).
cl_abap_unit_assert=>assert_equals( exp = abap_true act = mo_cut->is_zone_supported( `es` ) msg = `es` ).
ENDMETHOD.
METHOD unsupported_zones.
DATA lv_empty TYPE string.
cl_abap_unit_assert=>assert_equals( exp = abap_false act = mo_cut->is_zone_supported( `XX` ) msg = `XX` ).
cl_abap_unit_assert=>assert_equals( exp = abap_false act = mo_cut->is_zone_supported( lv_empty ) msg = `empty` ).
cl_abap_unit_assert=>assert_equals( exp = abap_false act = mo_cut->is_zone_supported( ` ` ) msg = `blanks` ).
cl_abap_unit_assert=>assert_equals( exp = abap_false act = mo_cut->is_zone_supported( `DEU` ) msg = `DEU` ).
cl_abap_unit_assert=>assert_equals( exp = abap_false act = mo_cut->is_zone_supported( `DE1` ) msg = `DE1` ).
ENDMETHOD.
METHOD all_rates.
assert_calculation( iv_weight_kg = '1.00' iv_zone = `DE`
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-ok base = '4.50' total = '4.50' ) ).
assert_calculation( iv_weight_kg = '1.00' iv_zone = `AT`
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-ok base = '5.20' total = '5.20' ) ).
assert_calculation( iv_weight_kg = '1.00' iv_zone = `CH`
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-ok base = '7.80' total = '7.80' ) ).
assert_calculation( iv_weight_kg = '1.00' iv_zone = `FR`
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-ok base = '6.40' total = '6.40' ) ).
assert_calculation( iv_weight_kg = '1.00' iv_zone = `NL`
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-ok base = '4.90' total = '4.90' ) ).
assert_calculation( iv_weight_kg = '1.00' iv_zone = `PL`
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-ok base = '5.10' total = '5.10' ) ).
assert_calculation( iv_weight_kg = '1.00' iv_zone = `ES`
is_expected = VALUE #( status = {{p}}freight_calculator=>c_status-ok base = '6.90' total = '6.90' ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,130 @@
CLASS {{p}}freight_calculator DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_result,
status TYPE c LENGTH 10,
base TYPE p LENGTH 9 DECIMALS 2,
surcharge TYPE p LENGTH 9 DECIMALS 2,
total TYPE p LENGTH 9 DECIMALS 2,
END OF ty_result.
CONSTANTS:
BEGIN OF c_status,
ok TYPE c LENGTH 10 VALUE 'OK',
weight TYPE c LENGTH 10 VALUE 'WEIGHT',
zone TYPE c LENGTH 10 VALUE 'ZONE',
END OF c_status.
METHODS calculate
IMPORTING
iv_weight_kg TYPE p LENGTH 8 DECIMALS 2
iv_zone TYPE string
iv_express TYPE abap_bool DEFAULT abap_false
RETURNING
VALUE(rs_result) TYPE ty_result.
METHODS is_zone_supported
IMPORTING
iv_zone TYPE string
RETURNING
VALUE(rv_supported) TYPE abap_bool.
PRIVATE SECTION.
TYPES:
BEGIN OF ty_rate,
zone TYPE c LENGTH 2,
rate TYPE p LENGTH 6 DECIMALS 2,
END OF ty_rate,
tt_rate TYPE HASHED TABLE OF ty_rate WITH UNIQUE KEY zone.
CONSTANTS c_express_share TYPE p LENGTH 5 DECIMALS 2 VALUE '0.25'.
METHODS normalize_zone
IMPORTING iv_zone TYPE string
RETURNING VALUE(rv_zone) TYPE string.
METHODS rates
RETURNING VALUE(rt_rate) TYPE tt_rate.
METHODS rate_for_zone
IMPORTING iv_zone TYPE string
RETURNING VALUE(rv_rate) TYPE p LENGTH 6 DECIMALS 2.
METHODS empty_result
IMPORTING iv_status TYPE c LENGTH 10
RETURNING VALUE(rs_result) TYPE ty_result.
METHODS base_amount
IMPORTING iv_rate TYPE p LENGTH 6 DECIMALS 2
iv_weight_kg TYPE p LENGTH 8 DECIMALS 2
RETURNING VALUE(rv_base) TYPE p LENGTH 9 DECIMALS 2.
METHODS surcharge_amount
IMPORTING iv_base TYPE p LENGTH 9 DECIMALS 2
iv_express TYPE abap_bool
RETURNING VALUE(rv_surcharge) TYPE p LENGTH 9 DECIMALS 2.
ENDCLASS.
CLASS {{p}}freight_calculator IMPLEMENTATION.
METHOD calculate.
DATA(lv_zone) = normalize_zone( iv_zone ).
IF iv_weight_kg <= 0.
rs_result = empty_result( c_status-weight ).
RETURN.
ENDIF.
IF is_zone_supported( lv_zone ) = abap_false.
rs_result = empty_result( c_status-zone ).
RETURN.
ENDIF.
rs_result-base = base_amount( iv_rate = rate_for_zone( lv_zone )
iv_weight_kg = iv_weight_kg ).
rs_result-surcharge = surcharge_amount( iv_base = rs_result-base
iv_express = iv_express ).
rs_result-total = rs_result-base + rs_result-surcharge.
rs_result-status = c_status-ok.
ENDMETHOD.
METHOD is_zone_supported.
DATA(lv_zone) = normalize_zone( iv_zone ).
DATA(lt_rate) = rates( ).
rv_supported = xsdbool( line_exists( lt_rate[ zone = lv_zone ] ) ).
ENDMETHOD.
METHOD normalize_zone.
rv_zone = to_upper( replace( val = iv_zone sub = ` ` with = `` occ = 0 ) ).
ENDMETHOD.
METHOD rates.
rt_rate = VALUE #( ( zone = 'DE' rate = '4.50' )
( zone = 'AT' rate = '5.20' )
( zone = 'CH' rate = '7.80' )
( zone = 'FR' rate = '6.40' )
( zone = 'NL' rate = '4.90' )
( zone = 'PL' rate = '5.10' )
( zone = 'ES' rate = '6.90' ) ).
ENDMETHOD.
METHOD rate_for_zone.
DATA(lt_rate) = rates( ).
READ TABLE lt_rate WITH TABLE KEY zone = iv_zone INTO DATA(ls_rate).
rv_rate = ls_rate-rate.
ENDMETHOD.
METHOD empty_result.
rs_result = VALUE #( status = iv_status ).
ENDMETHOD.
METHOD base_amount.
DATA lv_amount TYPE p LENGTH 13 DECIMALS 4.
lv_amount = iv_rate * iv_weight_kg.
rv_base = round( val = lv_amount dec = 2 ).
ENDMETHOD.
METHOD surcharge_amount.
IF iv_express = abap_false.
RETURN.
ENDIF.
DATA lv_amount TYPE p LENGTH 13 DECIMALS 4.
lv_amount = iv_base * c_express_share.
rv_surcharge = round( val = lv_amount dec = 2 ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,56 @@
CLASS ltc_freight_calculator DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}freight_calculator.
METHODS setup.
METHODS base_parcel FOR TESTING.
METHODS express_parcel FOR TESTING.
METHODS zone_is_normalized FOR TESTING.
METHODS weight_error FOR TESTING.
METHODS unknown_zone FOR TESTING.
METHODS zone_support FOR TESTING.
ENDCLASS.
CLASS ltc_freight_calculator IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}freight_calculator( ).
ENDMETHOD.
METHOD base_parcel.
cl_abap_unit_assert=>assert_equals(
exp = VALUE {{p}}freight_calculator=>ty_result( status = 'OK' base = '45.00' total = '45.00' )
act = mo_cut->calculate( iv_weight_kg = '10.00' iv_zone = `DE` ) ).
ENDMETHOD.
METHOD express_parcel.
cl_abap_unit_assert=>assert_equals(
exp = VALUE {{p}}freight_calculator=>ty_result( status = 'OK'
base = '45.00'
surcharge = '11.25'
total = '56.25' )
act = mo_cut->calculate( iv_weight_kg = '10.00' iv_zone = `DE` iv_express = abap_true ) ).
ENDMETHOD.
METHOD zone_is_normalized.
cl_abap_unit_assert=>assert_equals(
exp = VALUE {{p}}freight_calculator=>ty_result( status = 'OK' base = '39.00' total = '39.00' )
act = mo_cut->calculate( iv_weight_kg = '5.00' iv_zone = ` ch ` ) ).
ENDMETHOD.
METHOD weight_error.
cl_abap_unit_assert=>assert_equals(
exp = VALUE {{p}}freight_calculator=>ty_result( status = 'WEIGHT' )
act = mo_cut->calculate( iv_weight_kg = '0.00' iv_zone = `DE` ) ).
ENDMETHOD.
METHOD unknown_zone.
cl_abap_unit_assert=>assert_equals(
exp = VALUE {{p}}freight_calculator=>ty_result( status = 'ZONE' )
act = mo_cut->calculate( iv_weight_kg = '5.00' iv_zone = `XX` ) ).
ENDMETHOD.
METHOD zone_support.
cl_abap_unit_assert=>assert_true( mo_cut->is_zone_supported( `de` ) ).
cl_abap_unit_assert=>assert_false( mo_cut->is_zone_supported( `XX` ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,106 @@
CLASS {{p}}freight_calculator DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_result,
status TYPE c LENGTH 10,
base TYPE p LENGTH 9 DECIMALS 2,
surcharge TYPE p LENGTH 9 DECIMALS 2,
total TYPE p LENGTH 9 DECIMALS 2,
END OF ty_result.
CONSTANTS:
BEGIN OF c_status,
ok TYPE c LENGTH 10 VALUE 'OK',
weight TYPE c LENGTH 10 VALUE 'WEIGHT',
zone TYPE c LENGTH 10 VALUE 'ZONE',
END OF c_status.
METHODS calculate
IMPORTING
iv_weight_kg TYPE p LENGTH 8 DECIMALS 2
iv_zone TYPE string
iv_express TYPE abap_bool DEFAULT abap_false
RETURNING
VALUE(rs_result) TYPE ty_result.
METHODS is_zone_supported
IMPORTING
iv_zone TYPE string
RETURNING
VALUE(rv_supported) TYPE abap_bool.
PRIVATE SECTION.
DATA mv_last_zone TYPE string.
CONSTANTS c_express_share TYPE p LENGTH 5 DECIMALS 2 VALUE '0.25'.
ENDCLASS.
CLASS {{p}}freight_calculator IMPLEMENTATION.
METHOD calculate.
DATA lv_zone TYPE string.
DATA lv_rate TYPE p LENGTH 6 DECIMALS 2.
DATA lv_amount TYPE p LENGTH 13 DECIMALS 4.
DATA lv_express TYPE abap_bool.
lv_zone = to_upper( replace( val = iv_zone sub = ` ` with = `` occ = 0 ) ).
mv_last_zone = lv_zone.
IF iv_weight_kg <= 0.
rs_result-status = 'WEIGHT'.
rs_result-base = 0.
rs_result-surcharge = 0.
rs_result-total = 0.
RETURN.
ENDIF.
* IF lv_zone = 'BE'.
* lv_rate = '5.60'.
* ENDIF.
IF lv_zone = 'DE'.
lv_rate = '4.50'.
ELSEIF lv_zone = 'AT'.
lv_rate = '5.20'.
ELSEIF lv_zone = 'CH'.
lv_rate = '7.80'.
ELSEIF lv_zone = 'FR'.
lv_rate = '6.40'.
ELSEIF lv_zone = 'NL'.
lv_rate = '4.90'.
ELSEIF lv_zone = 'PL'.
lv_rate = '5.10'.
ELSEIF lv_zone = 'ES'.
lv_rate = '6.90'.
ELSE.
rs_result-status = 'ZONE'.
rs_result-base = 0.
rs_result-surcharge = 0.
rs_result-total = 0.
RETURN.
ENDIF.
lv_amount = lv_rate * iv_weight_kg.
rs_result-base = round( val = lv_amount dec = 2 ).
lv_express = iv_express.
IF lv_express = abap_true.
lv_amount = rs_result-base * c_express_share.
rs_result-surcharge = round( val = lv_amount dec = 2 ).
ELSE.
rs_result-surcharge = 0.
ENDIF.
rs_result-total = rs_result-base + rs_result-surcharge.
rs_result-status = 'OK'.
ENDMETHOD.
METHOD is_zone_supported.
DATA lv_zone TYPE string.
lv_zone = to_upper( replace( val = iv_zone sub = ` ` with = `` occ = 0 ) ).
IF lv_zone = 'DE' OR lv_zone = 'AT' OR lv_zone = 'CH' OR lv_zone = 'FR'
OR lv_zone = 'NL' OR lv_zone = 'PL' OR lv_zone = 'ES'.
rv_supported = abap_true.
ELSE.
rv_supported = abap_false.
ENDIF.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,25 @@
CLASS ltc_legacy_freight DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS base_parcel FOR TESTING.
METHODS express_parcel FOR TESTING.
ENDCLASS.
CLASS ltc_legacy_freight IMPLEMENTATION.
METHOD base_parcel.
DATA(lo_cut) = NEW {{p}}freight_calculator( ).
cl_abap_unit_assert=>assert_equals(
exp = VALUE {{p}}freight_calculator=>ty_result( status = 'OK' base = '45.00' total = '45.00' )
act = lo_cut->calculate( iv_weight_kg = '10.00' iv_zone = `DE` ) ).
ENDMETHOD.
METHOD express_parcel.
DATA(lo_cut) = NEW {{p}}freight_calculator( ).
cl_abap_unit_assert=>assert_equals(
exp = VALUE {{p}}freight_calculator=>ty_result( status = 'OK'
base = '45.00'
surcharge = '11.25'
total = '56.25' )
act = lo_cut->calculate( iv_weight_kg = '10.00' iv_zone = `DE` iv_express = abap_true ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,67 @@
# 1. Goal
Refactor the class {{P}}FREIGHT_CALCULATOR. The class calculates the freight cost of a
parcel. The calculation is correct, but the code is hard to read and hard to maintain.
Make the code clean. The behavior and the public API must not change.
# 2. Open questions
None.
# 3. Context
- The class {{P}}FREIGHT_CALCULATOR exists in package $TMP. It is active.
- The class contains legacy code: one long method, nested IF blocks, hard-coded
numbers, a private attribute that is written but never read, and commented-out code.
- A logistics program calls the class. That program must continue to work without a
change. Therefore the behavior of the class must stay the same.
# 4. Contract
The public part of the class must stay exactly as it is:
- The class {{P}}FREIGHT_CALCULATOR is public, final, and has a public constructor
without parameters.
- Public type TY_RESULT with these components: STATUS TYPE c LENGTH 10,
BASE TYPE p LENGTH 9 DECIMALS 2, SURCHARGE TYPE p LENGTH 9 DECIMALS 2,
TOTAL TYPE p LENGTH 9 DECIMALS 2.
- Public constant structure C_STATUS with the components OK, WEIGHT and ZONE. Each
component has the type c LENGTH 10. The values are 'OK', 'WEIGHT' and 'ZONE'.
- Public method CALCULATE:
IMPORTING iv_weight_kg TYPE p LENGTH 8 DECIMALS 2
iv_zone TYPE string
iv_express TYPE abap_bool DEFAULT abap_false
RETURNING VALUE(rs_result) TYPE ty_result.
- Public method IS_ZONE_SUPPORTED:
IMPORTING iv_zone TYPE string
RETURNING VALUE(rv_supported) TYPE abap_bool.
- Do not add other public components or public methods.
# 5. Business rules
The rules below describe the behavior of the class. The behavior must not change.
1. Normalize the zone: remove all blanks and convert all letters to upper case. A
zone is known only if the normalized zone is exactly equal to a zone code. Do not
shorten the zone and do not extend the zone.
2. Check the weight first. If the weight is not greater than zero, return the status
WEIGHT with BASE, SURCHARGE and TOTAL equal to zero. This check comes before the
zone check.
3. If the normalized zone is not known, return the status ZONE with BASE, SURCHARGE
and TOTAL equal to zero.
4. The rate per kilogram depends on the zone: DE 4.50, AT 5.20, CH 7.80, FR 6.40,
NL 4.90, PL 5.10, ES 6.90.
5. BASE = rate x weight. Round BASE to 2 decimal places. Round half away from zero.
6. If iv_express is abap_true, SURCHARGE = BASE x 25 / 100, rounded to 2 decimal
places. Else SURCHARGE is zero.
7. TOTAL = BASE + SURCHARGE.
8. Return the status OK.
9. IS_ZONE_SUPPORTED returns abap_true if the normalized zone is known, else
abap_false. A blank zone is not known.
# 6. Constraints
- Release target: SAP_BASIS 816 (ABAP Platform 2025).
- Coding standards: Clean ABAP. Every method has less than 40 statements. No global
variables. No IF block nested deeper than 2 levels. No hard-coded rate in a
statement: use a constant or a table. Remove the private attribute that is never
read. Remove the commented-out code. No comment that restates the code.
- Out of scope: do not change the public API, do not change the behavior, do not
create new global objects, do not change the class name.
# 7. Acceptance
- The class is active and has no syntax error.
- The hidden tests pass.
- Write your own ABAP Unit tests as local test classes in the class.

View File

@@ -0,0 +1,54 @@
{
"id": "G0006",
"category": "E",
"object_type": "CLAS",
"difficulty": 2,
"release_target": "v816",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 60,
"max_activations": 15
},
"seed": [
{
"type": "CLAS",
"name": "{{P}}FREIGHT_CALCULATOR",
"file": "seed/freight_calculator.clas.abap",
"description": "Legacy freight calculator. Correct behavior, poor structure: one long method, nested IF blocks, hard-coded rates, a private attribute that is written but never read, commented-out code."
}
],
"contract": [
{
"type": "CLAS",
"name": "{{P}}FREIGHT_CALCULATOR"
}
],
"out_of_scope": [
"New global objects",
"Changes to the public API of {{P}}FREIGHT_CALCULATOR",
"Changes to the result of the calculation"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T02_HIDDEN",
"file": "hidden/t02_hidden.clas.abap",
"description": "T02 hidden tests for the refactored freight calculator"
}
],
"reference": [
{
"type": "CLAS",
"name": "{{P}}FREIGHT_CALCULATOR",
"file": "reference/freight_calculator.clas.abap",
"description": "Refactored freight calculator with extracted methods and a rate table",
"testclasses_file": "reference/freight_calculator.testclasses.abap"
}
],
"craft_checks": [
"method_length",
"nesting_depth",
"magic_number",
"dead_code"
]
}

View File

@@ -0,0 +1,14 @@
{
"id": "G0007",
"pool": "eval",
"object_type": "CLAS",
"category": "F",
"attempts": [
{
"stage": "validate",
"oracle": 100.0,
"null": 0
}
],
"accepted": true
}

View File

@@ -0,0 +1,103 @@
CLASS {{p}}t02_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}if_loyalty_rules.
METHODS setup.
METHODS tier_boundaries FOR TESTING.
METHODS tier_negative FOR TESTING.
METHODS tier_zero FOR TESTING.
METHODS discount_all_tiers FOR TESTING.
METHODS discount_unknown_tier FOR TESTING.
METHODS next_tier_from_basic FOR TESTING.
METHODS next_tier_from_silver FOR TESTING.
METHODS next_tier_from_gold FOR TESTING.
METHODS next_tier_from_platinum FOR TESTING.
METHODS next_tier_negative_points FOR TESTING.
ENDCLASS.
CLASS {{p}}t02_hidden IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}loyalty_evaluator( ).
ENDMETHOD.
METHOD tier_boundaries.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_loyalty_rules=>c_tier-basic
act = mo_cut->get_tier( 999 ) msg = `999` ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_loyalty_rules=>c_tier-silver
act = mo_cut->get_tier( 1000 ) msg = `1000` ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_loyalty_rules=>c_tier-silver
act = mo_cut->get_tier( 4999 ) msg = `4999` ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_loyalty_rules=>c_tier-gold
act = mo_cut->get_tier( 5000 ) msg = `5000` ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_loyalty_rules=>c_tier-gold
act = mo_cut->get_tier( 19999 ) msg = `19999` ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_loyalty_rules=>c_tier-platinum
act = mo_cut->get_tier( 20000 ) msg = `20000` ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_loyalty_rules=>c_tier-platinum
act = mo_cut->get_tier( 500000 ) msg = `500000` ).
ENDMETHOD.
METHOD tier_negative.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_loyalty_rules=>c_tier-basic
act = mo_cut->get_tier( -1 ) ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_loyalty_rules=>c_tier-basic
act = mo_cut->get_tier( -5000 ) ).
ENDMETHOD.
METHOD tier_zero.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_loyalty_rules=>c_tier-basic
act = mo_cut->get_tier( 0 ) ).
ENDMETHOD.
METHOD discount_all_tiers.
cl_abap_unit_assert=>assert_equals( exp = 0
act = mo_cut->get_discount( {{p}}if_loyalty_rules=>c_tier-basic ) msg = `basic` ).
cl_abap_unit_assert=>assert_equals( exp = 3
act = mo_cut->get_discount( {{p}}if_loyalty_rules=>c_tier-silver ) msg = `silver` ).
cl_abap_unit_assert=>assert_equals( exp = 7
act = mo_cut->get_discount( {{p}}if_loyalty_rules=>c_tier-gold ) msg = `gold` ).
cl_abap_unit_assert=>assert_equals( exp = 12
act = mo_cut->get_discount( {{p}}if_loyalty_rules=>c_tier-platinum ) msg = `platinum` ).
ENDMETHOD.
METHOD discount_unknown_tier.
cl_abap_unit_assert=>assert_equals( exp = 0
act = mo_cut->get_discount( 'X' ) ).
cl_abap_unit_assert=>assert_equals( exp = 0
act = mo_cut->get_discount( ' ' ) ).
ENDMETHOD.
METHOD next_tier_from_basic.
cl_abap_unit_assert=>assert_equals( exp = 1000
act = mo_cut->points_to_next_tier( 0 ) msg = `0` ).
cl_abap_unit_assert=>assert_equals( exp = 1
act = mo_cut->points_to_next_tier( 999 ) msg = `999` ).
ENDMETHOD.
METHOD next_tier_from_silver.
cl_abap_unit_assert=>assert_equals( exp = 4000
act = mo_cut->points_to_next_tier( 1000 ) msg = `1000` ).
cl_abap_unit_assert=>assert_equals( exp = 1
act = mo_cut->points_to_next_tier( 4999 ) msg = `4999` ).
ENDMETHOD.
METHOD next_tier_from_gold.
cl_abap_unit_assert=>assert_equals( exp = 15000
act = mo_cut->points_to_next_tier( 5000 ) msg = `5000` ).
cl_abap_unit_assert=>assert_equals( exp = 1
act = mo_cut->points_to_next_tier( 19999 ) msg = `19999` ).
ENDMETHOD.
METHOD next_tier_from_platinum.
cl_abap_unit_assert=>assert_equals( exp = 0
act = mo_cut->points_to_next_tier( 20000 ) ).
cl_abap_unit_assert=>assert_equals( exp = 0
act = mo_cut->points_to_next_tier( 999999 ) ).
ENDMETHOD.
METHOD next_tier_negative_points.
cl_abap_unit_assert=>assert_equals( exp = 1000
act = mo_cut->points_to_next_tier( -7 ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,79 @@
CLASS {{p}}loyalty_evaluator DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_loyalty_rules.
PRIVATE SECTION.
CONSTANTS:
c_silver_threshold TYPE i VALUE 1000,
c_gold_threshold TYPE i VALUE 5000,
c_platinum_threshold TYPE i VALUE 20000.
METHODS normalize_points
IMPORTING iv_points TYPE i
RETURNING VALUE(rv_points) TYPE i.
METHODS tier_of
IMPORTING iv_points TYPE i
RETURNING VALUE(rv_tier) TYPE {{p}}if_loyalty_rules=>ty_tier.
METHODS points_to_tier
IMPORTING iv_points TYPE i
iv_tier TYPE {{p}}if_loyalty_rules=>ty_tier
RETURNING VALUE(rv_points) TYPE i.
ENDCLASS.
CLASS {{p}}loyalty_evaluator IMPLEMENTATION.
METHOD {{p}}if_loyalty_rules~get_tier.
rv_tier = tier_of( normalize_points( iv_points ) ).
ENDMETHOD.
METHOD {{p}}if_loyalty_rules~get_discount.
CASE iv_tier.
WHEN {{p}}if_loyalty_rules=>c_tier-silver.
rv_percent = 3.
WHEN {{p}}if_loyalty_rules=>c_tier-gold.
rv_percent = 7.
WHEN {{p}}if_loyalty_rules=>c_tier-platinum.
rv_percent = 12.
WHEN OTHERS.
rv_percent = 0.
ENDCASE.
ENDMETHOD.
METHOD {{p}}if_loyalty_rules~points_to_next_tier.
DATA(lv_points) = normalize_points( iv_points ).
DATA(lv_tier) = tier_of( lv_points ).
rv_points = points_to_tier( iv_points = lv_points
iv_tier = lv_tier ).
ENDMETHOD.
METHOD normalize_points.
rv_points = COND #( WHEN iv_points < 0 THEN 0 ELSE iv_points ).
ENDMETHOD.
METHOD tier_of.
IF iv_points >= c_platinum_threshold.
rv_tier = {{p}}if_loyalty_rules=>c_tier-platinum.
ELSEIF iv_points >= c_gold_threshold.
rv_tier = {{p}}if_loyalty_rules=>c_tier-gold.
ELSEIF iv_points >= c_silver_threshold.
rv_tier = {{p}}if_loyalty_rules=>c_tier-silver.
ELSE.
rv_tier = {{p}}if_loyalty_rules=>c_tier-basic.
ENDIF.
ENDMETHOD.
METHOD points_to_tier.
CASE iv_tier.
WHEN {{p}}if_loyalty_rules=>c_tier-basic.
rv_points = c_silver_threshold - iv_points.
WHEN {{p}}if_loyalty_rules=>c_tier-silver.
rv_points = c_gold_threshold - iv_points.
WHEN {{p}}if_loyalty_rules=>c_tier-gold.
rv_points = c_platinum_threshold - iv_points.
WHEN OTHERS.
rv_points = 0.
ENDCASE.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,77 @@
CLASS ltc_loyalty_evaluator DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}if_loyalty_rules.
METHODS setup.
METHODS tier_of_boundary_points FOR TESTING.
METHODS negative_points_are_basic FOR TESTING.
METHODS discount_of_each_tier FOR TESTING.
METHODS discount_of_unknown_tier FOR TESTING.
METHODS points_to_silver FOR TESTING.
METHODS points_to_gold FOR TESTING.
METHODS points_to_platinum FOR TESTING.
METHODS platinum_needs_no_points FOR TESTING.
ENDCLASS.
CLASS ltc_loyalty_evaluator IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}loyalty_evaluator( ).
ENDMETHOD.
METHOD tier_of_boundary_points.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_loyalty_rules=>c_tier-basic
act = mo_cut->get_tier( 0 ) ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_loyalty_rules=>c_tier-silver
act = mo_cut->get_tier( 1000 ) ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_loyalty_rules=>c_tier-gold
act = mo_cut->get_tier( 5000 ) ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_loyalty_rules=>c_tier-platinum
act = mo_cut->get_tier( 20000 ) ).
ENDMETHOD.
METHOD negative_points_are_basic.
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_loyalty_rules=>c_tier-basic
act = mo_cut->get_tier( -100 ) ).
ENDMETHOD.
METHOD discount_of_each_tier.
cl_abap_unit_assert=>assert_equals( exp = 0
act = mo_cut->get_discount( {{p}}if_loyalty_rules=>c_tier-basic ) ).
cl_abap_unit_assert=>assert_equals( exp = 3
act = mo_cut->get_discount( {{p}}if_loyalty_rules=>c_tier-silver ) ).
cl_abap_unit_assert=>assert_equals( exp = 7
act = mo_cut->get_discount( {{p}}if_loyalty_rules=>c_tier-gold ) ).
cl_abap_unit_assert=>assert_equals( exp = 12
act = mo_cut->get_discount( {{p}}if_loyalty_rules=>c_tier-platinum ) ).
ENDMETHOD.
METHOD discount_of_unknown_tier.
cl_abap_unit_assert=>assert_equals( exp = 0
act = mo_cut->get_discount( 'X' ) ).
ENDMETHOD.
METHOD points_to_silver.
cl_abap_unit_assert=>assert_equals( exp = 1000
act = mo_cut->points_to_next_tier( 0 ) ).
cl_abap_unit_assert=>assert_equals( exp = 1
act = mo_cut->points_to_next_tier( 999 ) ).
ENDMETHOD.
METHOD points_to_gold.
cl_abap_unit_assert=>assert_equals( exp = 4000
act = mo_cut->points_to_next_tier( 1000 ) ).
ENDMETHOD.
METHOD points_to_platinum.
cl_abap_unit_assert=>assert_equals( exp = 15000
act = mo_cut->points_to_next_tier( 5000 ) ).
ENDMETHOD.
METHOD platinum_needs_no_points.
cl_abap_unit_assert=>assert_equals( exp = 0
act = mo_cut->points_to_next_tier( 20000 ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,24 @@
INTERFACE {{p}}if_loyalty_rules PUBLIC.
TYPES ty_points TYPE i.
TYPES ty_tier TYPE c LENGTH 1.
CONSTANTS:
BEGIN OF c_tier,
basic TYPE ty_tier VALUE 'B',
silver TYPE ty_tier VALUE 'S',
gold TYPE ty_tier VALUE 'G',
platinum TYPE ty_tier VALUE 'P',
END OF c_tier.
METHODS get_tier
IMPORTING iv_points TYPE ty_points
RETURNING VALUE(rv_tier) TYPE ty_tier.
METHODS get_discount
IMPORTING iv_tier TYPE ty_tier
RETURNING VALUE(rv_percent) TYPE i.
METHODS points_to_next_tier
IMPORTING iv_points TYPE ty_points
RETURNING VALUE(rv_points) TYPE i.
ENDINTERFACE.

View File

@@ -0,0 +1,57 @@
# 1. Goal
A retail company gives loyalty discounts to its customers. A checkout program needs
three values: the loyalty tier of a customer, the discount percent of a tier, and
the number of points that a customer still needs for the next tier. The program
calls one class for these three values.
# 2. Open questions
None.
# 3. Context
- The interface {{P}}IF_LOYALTY_RULES exists in package $TMP. It is active.
- The interface declares the type of a point value, the type of a tier, the
constants for all tiers, and three methods. This document does not repeat the
signature of the interface. Read the interface before you start.
- The three methods are:
* the method that returns the tier for a point value,
* the method that returns the discount percent of a tier,
* the method that returns the number of points that are missing for the next tier.
# 4. Contract
- Create the class {{P}}LOYALTY_EVALUATOR in package $TMP.
- The class is public and final. It has a public constructor without parameters.
- The class implements {{P}}IF_LOYALTY_RULES.
- Do not add other public methods.
# 5. Business rules
Tier of a point value:
1. A point value below zero counts as zero.
2. 0 to 999 points is the basic tier.
3. 1000 to 4999 points is the silver tier.
4. 5000 to 19999 points is the gold tier.
5. 20000 points and more is the platinum tier.
Discount percent of a tier:
6. basic is 0 percent.
7. silver is 3 percent.
8. gold is 7 percent.
9. platinum is 12 percent.
10. Any other tier value is 0 percent.
Points to the next tier:
11. A point value below zero counts as zero.
12. basic needs 1000 points minus the point value.
13. silver needs 5000 points minus the point value.
14. gold needs 20000 points minus the point value.
15. platinum needs 0 points.
# 6. Constraints
- Release target: 8.16.
- Coding standards: Clean ABAP. Methods below 40 statements. No global variables.
No comment that restates the code.
- Out of scope: do not change {{P}}IF_LOYALTY_RULES.
# 7. Acceptance
- The class is active and has no syntax error.
- The hidden tests pass.
- Write your own ABAP Unit tests for the class.

View File

@@ -0,0 +1,51 @@
{
"id": "G0007",
"category": "F",
"object_type": "CLAS",
"difficulty": 2,
"release_target": "v816",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 40,
"max_activations": 10
},
"seed": [
{
"type": "INTF",
"name": "{{P}}IF_LOYALTY_RULES",
"file": "seed/if_loyalty_rules.intf.abap",
"description": "Loyalty contract: tier type, point type, tier constants and three methods. The spec does not repeat the signature."
}
],
"contract": [
{
"type": "CLAS",
"name": "{{P}}LOYALTY_EVALUATOR",
"implements": "{{P}}IF_LOYALTY_RULES"
}
],
"out_of_scope": [
"{{P}}IF_LOYALTY_RULES"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T02_HIDDEN",
"file": "hidden/t02_hidden.clas.abap",
"description": "T02 hidden tests"
}
],
"reference": [
{
"type": "CLAS",
"name": "{{P}}LOYALTY_EVALUATOR",
"file": "reference/loyalty_evaluator.clas.abap",
"description": "Loyalty evaluator",
"testclasses_file": "reference/loyalty_evaluator.testclasses.abap"
}
],
"craft_checks": [
"method_length",
"nesting_depth"
]
}

View File

@@ -0,0 +1,19 @@
{
"id": "G0008",
"pool": "eval",
"object_type": "CLAS",
"category": "G",
"attempts": [
{
"stage": "validate",
"oracle": 0,
"null": 0
},
{
"stage": "validate",
"oracle": 100.0,
"null": 0
}
],
"accepted": true
}

View File

@@ -0,0 +1,197 @@
CLASS {{p}}t07_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}if_freight_calc.
METHODS setup.
METHODS assert_amount
IMPORTING iv_expected TYPE {{p}}if_freight_calc=>ty_amount
iv_actual TYPE {{p}}if_freight_calc=>ty_amount.
METHODS assert_weight
IMPORTING iv_expected TYPE {{p}}if_freight_calc=>ty_weight
iv_actual TYPE {{p}}if_freight_calc=>ty_weight.
METHODS weight_not_positive FOR TESTING.
METHODS volume_negative FOR TESTING.
METHODS distance_not_positive FOR TESTING.
METHODS fuel_index_negative FOR TESTING.
METHODS fuel_index_too_high FOR TESTING.
METHODS base_at_weight_limit FOR TESTING.
METHODS base_above_weight_limit FOR TESTING.
METHODS volumetric_weight_wins FOR TESTING.
METHODS distance_component FOR TESTING.
METHODS fuel_rounds_half_up FOR TESTING.
METHODS full_load FOR TESTING.
ENDCLASS.
CLASS {{p}}t07_hidden IMPLEMENTATION.
METHOD setup.
DATA lo_calc TYPE REF TO {{p}}freight_calc.
CREATE OBJECT lo_calc.
mo_cut = lo_calc.
ENDMETHOD.
METHOD assert_amount.
cl_abap_unit_assert=>assert_equals( exp = iv_expected act = iv_actual ).
ENDMETHOD.
METHOD assert_weight.
cl_abap_unit_assert=>assert_equals( exp = iv_expected act = iv_actual ).
ENDMETHOD.
METHOD weight_not_positive.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = 0
iv_volume_m3 = -1
iv_distance_km = 0
iv_fuel_index = 200 ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_freight_calc=>c_reason-weight
act = ls_result-reason ).
assert_amount( iv_expected = 0 iv_actual = ls_result-total ).
assert_weight( iv_expected = 0 iv_actual = ls_result-chargeable_weight ).
ENDMETHOD.
METHOD volume_negative.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = 1000
iv_volume_m3 = '-0.5'
iv_distance_km = 100
iv_fuel_index = 50 ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_freight_calc=>c_reason-volume
act = ls_result-reason ).
assert_amount( iv_expected = 0 iv_actual = ls_result-total ).
ENDMETHOD.
METHOD distance_not_positive.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = 1000
iv_volume_m3 = 0
iv_distance_km = 0
iv_fuel_index = 50 ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_freight_calc=>c_reason-distance
act = ls_result-reason ).
assert_amount( iv_expected = 0 iv_actual = ls_result-total ).
ENDMETHOD.
METHOD fuel_index_negative.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = 1000
iv_volume_m3 = 0
iv_distance_km = 100
iv_fuel_index = '-0.1' ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_freight_calc=>c_reason-fuel
act = ls_result-reason ).
assert_amount( iv_expected = 0 iv_actual = ls_result-total ).
ENDMETHOD.
METHOD fuel_index_too_high.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = 1000
iv_volume_m3 = 0
iv_distance_km = 100
iv_fuel_index = '100.1' ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_freight_calc=>c_reason-fuel
act = ls_result-reason ).
assert_amount( iv_expected = 0 iv_actual = ls_result-total ).
ENDMETHOD.
METHOD base_at_weight_limit.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = 1000
iv_volume_m3 = 0
iv_distance_km = 100
iv_fuel_index = 0 ).
assert_weight( iv_expected = 1000 iv_actual = ls_result-chargeable_weight ).
assert_amount( iv_expected = 45 iv_actual = ls_result-base ).
assert_amount( iv_expected = 0 iv_actual = ls_result-distance ).
assert_amount( iv_expected = 0 iv_actual = ls_result-fuel ).
assert_amount( iv_expected = 45 iv_actual = ls_result-total ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_freight_calc=>c_reason-ok
act = ls_result-reason ).
ENDMETHOD.
METHOD base_above_weight_limit.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = '1100.5'
iv_volume_m3 = 0
iv_distance_km = 100
iv_fuel_index = 0 ).
assert_weight( iv_expected = '1100.5' iv_actual = ls_result-chargeable_weight ).
assert_amount( iv_expected = 58 iv_actual = ls_result-base ).
assert_amount( iv_expected = 58 iv_actual = ls_result-total ).
ENDMETHOD.
METHOD volumetric_weight_wins.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = 500
iv_volume_m3 = '4.5'
iv_distance_km = 100
iv_fuel_index = 0 ).
assert_weight( iv_expected = 1125 iv_actual = ls_result-chargeable_weight ).
assert_amount( iv_expected = 58 iv_actual = ls_result-base ).
assert_amount( iv_expected = 58 iv_actual = ls_result-total ).
ENDMETHOD.
METHOD distance_component.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = 1000
iv_volume_m3 = 0
iv_distance_km = 350
iv_fuel_index = 0 ).
assert_amount( iv_expected = 45 iv_actual = ls_result-base ).
assert_amount( iv_expected = 27 iv_actual = ls_result-distance ).
assert_amount( iv_expected = 72 iv_actual = ls_result-total ).
ENDMETHOD.
METHOD fuel_rounds_half_up.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = 1000
iv_volume_m3 = 0
iv_distance_km = 100
iv_fuel_index = '12.3' ).
assert_amount( iv_expected = 45 iv_actual = ls_result-base ).
assert_amount( iv_expected = 0 iv_actual = ls_result-distance ).
assert_amount( iv_expected = '5.54' iv_actual = ls_result-fuel ).
assert_amount( iv_expected = '50.54' iv_actual = ls_result-total ).
ENDMETHOD.
METHOD full_load.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = 2000
iv_volume_m3 = 1
iv_distance_km = 500
iv_fuel_index = 100 ).
assert_weight( iv_expected = 2000 iv_actual = ls_result-chargeable_weight ).
assert_amount( iv_expected = 110 iv_actual = ls_result-base ).
assert_amount( iv_expected = 36 iv_actual = ls_result-distance ).
assert_amount( iv_expected = 146 iv_actual = ls_result-fuel ).
assert_amount( iv_expected = 292 iv_actual = ls_result-total ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_freight_calc=>c_reason-ok
act = ls_result-reason ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,149 @@
CLASS {{p}}freight_calc DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_freight_calc.
PRIVATE SECTION.
TYPES ty_quantity TYPE p LENGTH 13 DECIMALS 3.
CONSTANTS:
c_base_amount TYPE {{p}}if_freight_calc=>ty_amount VALUE '45.00',
c_base_weight TYPE ty_quantity VALUE '1000.000',
c_weight_rate TYPE {{p}}if_freight_calc=>ty_amount VALUE '6.50',
c_free_km TYPE i VALUE 100,
c_km_rate TYPE {{p}}if_freight_calc=>ty_amount VALUE '9.00',
c_step TYPE ty_quantity VALUE '100.000',
c_volume_factor TYPE i VALUE 250,
c_percent TYPE p LENGTH 3 DECIMALS 2 VALUE '0.01'.
METHODS check_input
IMPORTING iv_weight_kg TYPE {{p}}if_freight_calc=>ty_weight
iv_volume_m3 TYPE {{p}}if_freight_calc=>ty_volume
iv_distance_km TYPE {{p}}if_freight_calc=>ty_distance
iv_fuel_index TYPE {{p}}if_freight_calc=>ty_fuel
RETURNING VALUE(rv_reason) TYPE {{p}}if_freight_calc=>ty_reason.
METHODS chargeable_weight
IMPORTING iv_weight_kg TYPE {{p}}if_freight_calc=>ty_weight
iv_volume_m3 TYPE {{p}}if_freight_calc=>ty_volume
RETURNING VALUE(rv_weight) TYPE ty_quantity.
METHODS base_freight
IMPORTING iv_weight_kg TYPE ty_quantity
RETURNING VALUE(rv_amount) TYPE {{p}}if_freight_calc=>ty_amount.
METHODS distance_freight
IMPORTING iv_distance_km TYPE {{p}}if_freight_calc=>ty_distance
RETURNING VALUE(rv_amount) TYPE {{p}}if_freight_calc=>ty_amount.
METHODS fuel_freight
IMPORTING iv_amount TYPE {{p}}if_freight_calc=>ty_amount
iv_fuel_index TYPE {{p}}if_freight_calc=>ty_fuel
RETURNING VALUE(rv_amount) TYPE {{p}}if_freight_calc=>ty_amount.
METHODS started_steps
IMPORTING iv_excess TYPE ty_quantity
RETURNING VALUE(rv_steps) TYPE i.
ENDCLASS.
CLASS {{p}}freight_calc IMPLEMENTATION.
METHOD {{p}}if_freight_calc~calculate.
DATA lv_reason TYPE {{p}}if_freight_calc=>ty_reason.
DATA lv_weight TYPE ty_quantity.
DATA lv_net TYPE {{p}}if_freight_calc=>ty_amount.
CLEAR rs_result.
lv_reason = check_input( iv_weight_kg = iv_weight_kg
iv_volume_m3 = iv_volume_m3
iv_distance_km = iv_distance_km
iv_fuel_index = iv_fuel_index ).
IF lv_reason <> {{p}}if_freight_calc=>c_reason-ok.
rs_result-reason = lv_reason.
RETURN.
ENDIF.
lv_weight = chargeable_weight( iv_weight_kg = iv_weight_kg
iv_volume_m3 = iv_volume_m3 ).
rs_result-chargeable_weight = lv_weight.
rs_result-base = base_freight( lv_weight ).
rs_result-distance = distance_freight( iv_distance_km ).
lv_net = rs_result-base + rs_result-distance.
rs_result-fuel = fuel_freight( iv_amount = lv_net
iv_fuel_index = iv_fuel_index ).
rs_result-total = rs_result-base + rs_result-distance + rs_result-fuel.
rs_result-reason = {{p}}if_freight_calc=>c_reason-ok.
ENDMETHOD.
METHOD check_input.
IF iv_weight_kg <= 0.
rv_reason = {{p}}if_freight_calc=>c_reason-weight.
ELSEIF iv_volume_m3 < 0.
rv_reason = {{p}}if_freight_calc=>c_reason-volume.
ELSEIF iv_distance_km <= 0.
rv_reason = {{p}}if_freight_calc=>c_reason-distance.
ELSEIF iv_fuel_index < 0 OR iv_fuel_index > 100.
rv_reason = {{p}}if_freight_calc=>c_reason-fuel.
ELSE.
rv_reason = {{p}}if_freight_calc=>c_reason-ok.
ENDIF.
ENDMETHOD.
METHOD chargeable_weight.
DATA lv_volumetric TYPE ty_quantity.
lv_volumetric = iv_volume_m3 * c_volume_factor.
IF lv_volumetric > iv_weight_kg.
rv_weight = lv_volumetric.
ELSE.
rv_weight = iv_weight_kg.
ENDIF.
ENDMETHOD.
METHOD base_freight.
DATA lv_excess TYPE ty_quantity.
DATA lv_steps TYPE i.
lv_excess = iv_weight_kg - c_base_weight.
IF lv_excess <= 0.
rv_amount = c_base_amount.
ELSE.
lv_steps = started_steps( lv_excess ).
rv_amount = c_base_amount + lv_steps * c_weight_rate.
ENDIF.
ENDMETHOD.
METHOD distance_freight.
DATA lv_excess TYPE ty_quantity.
DATA lv_steps TYPE i.
lv_excess = iv_distance_km - c_free_km.
IF lv_excess <= 0.
rv_amount = 0.
ELSE.
lv_steps = started_steps( lv_excess ).
rv_amount = lv_steps * c_km_rate.
ENDIF.
ENDMETHOD.
METHOD fuel_freight.
DATA lv_scaled TYPE p LENGTH 15 DECIMALS 3.
DATA lv_fuel TYPE p LENGTH 15 DECIMALS 5.
lv_scaled = iv_amount * iv_fuel_index.
lv_fuel = lv_scaled * c_percent.
rv_amount = lv_fuel.
ENDMETHOD.
METHOD started_steps.
DATA lv_excess_scaled TYPE p LENGTH 16 DECIMALS 0.
DATA lv_step_scaled TYPE p LENGTH 16 DECIMALS 0.
lv_excess_scaled = iv_excess * 1000.
lv_step_scaled = c_step * 1000.
rv_steps = lv_excess_scaled DIV lv_step_scaled.
IF lv_excess_scaled MOD lv_step_scaled > 0.
rv_steps = rv_steps + 1.
ENDIF.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,114 @@
CLASS ltc_freight_calc DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}if_freight_calc.
METHODS setup.
METHODS assert_amount
IMPORTING iv_expected TYPE {{p}}if_freight_calc=>ty_amount
iv_actual TYPE {{p}}if_freight_calc=>ty_amount.
METHODS assert_weight
IMPORTING iv_expected TYPE {{p}}if_freight_calc=>ty_weight
iv_actual TYPE {{p}}if_freight_calc=>ty_weight.
METHODS base_and_distance FOR TESTING.
METHODS fuel_is_rounded FOR TESTING.
METHODS volume_sets_chargeable_weight FOR TESTING.
METHODS weight_zero_is_rejected FOR TESTING.
METHODS distance_zero_is_rejected FOR TESTING.
METHODS fuel_index_out_of_range FOR TESTING.
ENDCLASS.
CLASS ltc_freight_calc IMPLEMENTATION.
METHOD setup.
DATA lo_calc TYPE REF TO {{p}}freight_calc.
CREATE OBJECT lo_calc.
mo_cut = lo_calc.
ENDMETHOD.
METHOD assert_amount.
cl_abap_unit_assert=>assert_equals( exp = iv_expected act = iv_actual ).
ENDMETHOD.
METHOD assert_weight.
cl_abap_unit_assert=>assert_equals( exp = iv_expected act = iv_actual ).
ENDMETHOD.
METHOD base_and_distance.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = 2000
iv_volume_m3 = 1
iv_distance_km = 500
iv_fuel_index = 100 ).
assert_amount( iv_expected = 110 iv_actual = ls_result-base ).
assert_amount( iv_expected = 36 iv_actual = ls_result-distance ).
assert_amount( iv_expected = 146 iv_actual = ls_result-fuel ).
assert_amount( iv_expected = 292 iv_actual = ls_result-total ).
ENDMETHOD.
METHOD fuel_is_rounded.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = 1000
iv_volume_m3 = 0
iv_distance_km = 100
iv_fuel_index = '12.3' ).
assert_amount( iv_expected = '5.54' iv_actual = ls_result-fuel ).
assert_amount( iv_expected = '50.54' iv_actual = ls_result-total ).
ENDMETHOD.
METHOD volume_sets_chargeable_weight.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = 500
iv_volume_m3 = '4.5'
iv_distance_km = 100
iv_fuel_index = 0 ).
assert_weight( iv_expected = 1125 iv_actual = ls_result-chargeable_weight ).
assert_amount( iv_expected = 58 iv_actual = ls_result-base ).
ENDMETHOD.
METHOD weight_zero_is_rejected.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = 0
iv_volume_m3 = 2
iv_distance_km = 300
iv_fuel_index = 10 ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_freight_calc=>c_reason-weight
act = ls_result-reason ).
assert_amount( iv_expected = 0 iv_actual = ls_result-total ).
ENDMETHOD.
METHOD distance_zero_is_rejected.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = 1500
iv_volume_m3 = 0
iv_distance_km = 0
iv_fuel_index = 10 ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_freight_calc=>c_reason-distance
act = ls_result-reason ).
assert_amount( iv_expected = 0 iv_actual = ls_result-total ).
ENDMETHOD.
METHOD fuel_index_out_of_range.
DATA ls_result TYPE {{p}}if_freight_calc=>ty_result.
ls_result = mo_cut->calculate( iv_weight_kg = 1500
iv_volume_m3 = 0
iv_distance_km = 300
iv_fuel_index = 120 ).
cl_abap_unit_assert=>assert_equals( exp = {{p}}if_freight_calc=>c_reason-fuel
act = ls_result-reason ).
assert_amount( iv_expected = 0 iv_actual = ls_result-total ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,34 @@
INTERFACE {{p}}if_freight_calc PUBLIC.
TYPES ty_reason TYPE c LENGTH 10.
TYPES ty_amount TYPE p LENGTH 9 DECIMALS 2.
TYPES ty_weight TYPE p LENGTH 9 DECIMALS 3.
TYPES ty_volume TYPE p LENGTH 9 DECIMALS 3.
TYPES ty_distance TYPE i.
TYPES ty_fuel TYPE p LENGTH 5 DECIMALS 1.
CONSTANTS:
BEGIN OF c_reason,
ok TYPE ty_reason VALUE 'OK',
weight TYPE ty_reason VALUE 'WEIGHT',
volume TYPE ty_reason VALUE 'VOLUME',
distance TYPE ty_reason VALUE 'DISTANCE',
fuel TYPE ty_reason VALUE 'FUEL',
END OF c_reason.
TYPES:
BEGIN OF ty_result,
chargeable_weight TYPE ty_weight,
base TYPE ty_amount,
distance TYPE ty_amount,
fuel TYPE ty_amount,
total TYPE ty_amount,
reason TYPE ty_reason,
END OF ty_result.
METHODS calculate
IMPORTING iv_weight_kg TYPE ty_weight
iv_volume_m3 TYPE ty_volume
iv_distance_km TYPE ty_distance
iv_fuel_index TYPE ty_fuel
RETURNING VALUE(rs_result) TYPE ty_result.
ENDINTERFACE.

View File

@@ -0,0 +1,61 @@
# 1. Goal
A freight forwarder calculates the price of an outbound shipment. The
calculation service receives the gross weight, the volume, the transport
distance, and the current fuel index. It returns the freight components and a
reason code.
# 2. Open questions
None.
# 3. Context
- The interface {{P}}IF_FREIGHT_CALC exists in package $TMP. It is active.
- The interface defines the types TY_REASON, TY_AMOUNT, TY_WEIGHT, TY_VOLUME,
TY_DISTANCE, TY_FUEL, and TY_RESULT.
- The interface defines the constant structure C_REASON with the components
OK, WEIGHT, VOLUME, DISTANCE, and FUEL.
- The interface defines the instance method CALCULATE with the importing
parameters IV_WEIGHT_KG, IV_VOLUME_M3, IV_DISTANCE_KM, IV_FUEL_INDEX and the
returning parameter RS_RESULT.
# 4. Contract
- Create the class {{P}}FREIGHT_CALC in package $TMP.
- The class is public, final, and has a public constructor without parameters.
- The class implements {{P}}IF_FREIGHT_CALC.
- Do not add other public methods.
# 5. Business rules
All amounts are in euro. Apply the rules in this sequence.
1. If IV_WEIGHT_KG is less than or equal to 0, return the reason WEIGHT. Set
all amounts and the chargeable weight to 0.
2. If IV_VOLUME_M3 is less than 0, return the reason VOLUME. Set all amounts
and the chargeable weight to 0.
3. If IV_DISTANCE_KM is less than or equal to 0, return the reason DISTANCE.
Set all amounts and the chargeable weight to 0.
4. If IV_FUEL_INDEX is less than 0 or greater than 100, return the reason FUEL.
Set all amounts and the chargeable weight to 0.
5. The volumetric weight is IV_VOLUME_M3 multiplied by 250.
6. The chargeable weight is the greater of IV_WEIGHT_KG and the volumetric
weight.
7. The base freight is 45.00. For each full or partial 100 kg by which the
chargeable weight is greater than 1000 kg, add 6.50.
8. The distance freight is 0.00 for a distance up to and including 100 km. For
each full or partial 100 km by which IV_DISTANCE_KM is greater than 100 km,
add 9.00.
9. The fuel freight is the sum of the base freight and the distance freight,
multiplied by IV_FUEL_INDEX, divided by 100. Round the result to 2 decimal
places. Round a half away from zero.
10. The total is the sum of the base freight, the distance freight, and the
fuel freight.
11. Return the reason OK.
# 6. Constraints
- Release target: 7.40 SP05. Do not use syntax that is newer than 7.40 SP05.
For example, do not use COND, SWITCH, REDUCE, FOR, or LET.
- Coding standards: Clean ABAP. Method length below 40 statements. No global
variables. No comment that restates the code.
- Out of scope: do not change {{P}}IF_FREIGHT_CALC.
# 7. Acceptance
- The class is active and has no syntax error.
- The hidden tests pass.
- Write your own ABAP Unit tests for the class.

View File

@@ -0,0 +1,51 @@
{
"id": "G0008",
"category": "G",
"object_type": "CLAS",
"difficulty": 2,
"release_target": "v740sp05",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 45,
"max_activations": 12
},
"seed": [
{
"type": "INTF",
"name": "{{P}}IF_FREIGHT_CALC",
"file": "seed/if_freight_calc.intf.abap",
"description": "Freight calculation contract with result type and reason codes"
}
],
"contract": [
{
"type": "CLAS",
"name": "{{P}}FREIGHT_CALC",
"implements": "{{P}}IF_FREIGHT_CALC"
}
],
"out_of_scope": [
"{{P}}IF_FREIGHT_CALC"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T07_HIDDEN",
"file": "hidden/t07_hidden.clas.abap",
"description": "T07 hidden tests for the freight calculation"
}
],
"reference": [
{
"type": "CLAS",
"name": "{{P}}FREIGHT_CALC",
"file": "reference/freight_calc.clas.abap",
"description": "Freight surcharge calculator",
"testclasses_file": "reference/freight_calc.testclasses.abap"
}
],
"craft_checks": [
"method_length",
"nesting_depth"
]
}

View File

@@ -0,0 +1,19 @@
{
"id": "G0009",
"pool": "eval",
"object_type": "CLAS",
"category": "I",
"attempts": [
{
"stage": "validate",
"oracle": 0,
"null": 0
},
{
"stage": "validate",
"oracle": 100.0,
"null": 0
}
],
"accepted": true
}

View File

@@ -0,0 +1,111 @@
CLASS {{p}}t02_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}if_shipping_cost.
METHODS setup.
METHODS zero_weight_is_free FOR TESTING.
METHODS light_parcel_gets_minimum FOR TESTING.
METHODS tier_limit_one_kg FOR TESTING.
METHODS tier_limit_five_kg FOR TESTING.
METHODS tier_limit_ten_kg FOR TESTING.
METHODS bulk_weight_limit FOR TESTING.
METHODS bulk_weight_started_kg FOR TESTING.
METHODS express_surcharge FOR TESTING.
METHODS express_below_minimum FOR TESTING.
METHODS bulk_express FOR TESTING.
METHODS assert_cost
IMPORTING iv_weight TYPE {{p}}if_shipping_cost=>ty_weight
iv_express TYPE abap_bool
iv_exp TYPE {{p}}if_shipping_cost=>ty_amount
iv_msg TYPE string.
ENDCLASS.
CLASS {{p}}t02_hidden IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}shipping_cost_calc( ).
ENDMETHOD.
METHOD assert_cost.
cl_abap_unit_assert=>assert_equals(
exp = iv_exp
act = mo_cut->calculate( iv_weight = iv_weight iv_express = iv_express )
msg = iv_msg ).
ENDMETHOD.
METHOD zero_weight_is_free.
assert_cost( iv_weight = '0.000' iv_express = abap_false
iv_exp = '0.00' iv_msg = `zero weight` ).
assert_cost( iv_weight = '-2.500' iv_express = abap_true
iv_exp = '0.00' iv_msg = `negative weight` ).
ENDMETHOD.
METHOD light_parcel_gets_minimum.
assert_cost( iv_weight = '0.100' iv_express = abap_false
iv_exp = '6.00' iv_msg = `0.100 kg` ).
assert_cost( iv_weight = '0.500' iv_express = abap_false
iv_exp = '6.00' iv_msg = `0.500 kg` ).
ENDMETHOD.
METHOD tier_limit_one_kg.
assert_cost( iv_weight = '1.000' iv_express = abap_false
iv_exp = '6.00' iv_msg = `1.000 kg` ).
assert_cost( iv_weight = '1.001' iv_express = abap_false
iv_exp = '8.50' iv_msg = `1.001 kg` ).
ENDMETHOD.
METHOD tier_limit_five_kg.
assert_cost( iv_weight = '5.000' iv_express = abap_false
iv_exp = '8.50' iv_msg = `5.000 kg` ).
assert_cost( iv_weight = '5.001' iv_express = abap_false
iv_exp = '12.00' iv_msg = `5.001 kg` ).
ENDMETHOD.
METHOD tier_limit_ten_kg.
assert_cost( iv_weight = '10.000' iv_express = abap_false
iv_exp = '12.00' iv_msg = `10.000 kg` ).
assert_cost( iv_weight = '10.001' iv_express = abap_false
iv_exp = '18.75' iv_msg = `10.001 kg` ).
ENDMETHOD.
METHOD bulk_weight_limit.
assert_cost( iv_weight = '31.500' iv_express = abap_false
iv_exp = '18.75' iv_msg = `31.500 kg` ).
assert_cost( iv_weight = '31.501' iv_express = abap_false
iv_exp = '19.40' iv_msg = `31.501 kg` ).
ENDMETHOD.
METHOD bulk_weight_started_kg.
assert_cost( iv_weight = '32.000' iv_express = abap_false
iv_exp = '19.40' iv_msg = `32.000 kg` ).
assert_cost( iv_weight = '33.000' iv_express = abap_false
iv_exp = '20.05' iv_msg = `33.000 kg` ).
assert_cost( iv_weight = '100.000' iv_express = abap_false
iv_exp = '63.60' iv_msg = `100.000 kg` ).
ENDMETHOD.
METHOD express_surcharge.
assert_cost( iv_weight = '2.000' iv_express = abap_true
iv_exp = '11.48' iv_msg = `2.000 kg express` ).
assert_cost( iv_weight = '20.000' iv_express = abap_true
iv_exp = '25.31' iv_msg = `20.000 kg express` ).
ENDMETHOD.
METHOD express_below_minimum.
assert_cost( iv_weight = '0.500' iv_express = abap_true
iv_exp = '6.62' iv_msg = `0.500 kg express` ).
assert_cost( iv_weight = '1.000' iv_express = abap_true
iv_exp = '6.62' iv_msg = `1.000 kg express` ).
ENDMETHOD.
METHOD bulk_express.
assert_cost( iv_weight = '32.000' iv_express = abap_true
iv_exp = '26.19' iv_msg = `32.000 kg express` ).
assert_cost( iv_weight = '100.000' iv_express = abap_true
iv_exp = '85.86' iv_msg = `100.000 kg express` ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,56 @@
CLASS {{p}}shipping_cost_calc DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_shipping_cost.
PRIVATE SECTION.
METHODS base_rate
IMPORTING iv_weight TYPE {{p}}if_shipping_cost=>ty_weight
RETURNING VALUE(rv_rate) TYPE {{p}}if_shipping_cost=>ty_amount.
METHODS express_surcharge
IMPORTING iv_rate TYPE {{p}}if_shipping_cost=>ty_amount
RETURNING VALUE(rv_surcharge) TYPE {{p}}if_shipping_cost=>ty_amount.
ENDCLASS.
CLASS {{p}}shipping_cost_calc IMPLEMENTATION.
METHOD {{p}}if_shipping_cost~calculate.
IF iv_weight <= 0.
rv_cost = 0.
RETURN.
ENDIF.
DATA(lv_cost) = base_rate( iv_weight ).
IF iv_express = abap_true.
lv_cost = lv_cost + express_surcharge( lv_cost ).
ENDIF.
IF lv_cost < {{p}}if_shipping_cost=>c_rate-min_charge.
rv_cost = {{p}}if_shipping_cost=>c_rate-min_charge.
ELSE.
rv_cost = lv_cost.
ENDIF.
ENDMETHOD.
METHOD base_rate.
IF iv_weight <= 1.
rv_rate = '4.90'.
ELSEIF iv_weight <= 5.
rv_rate = '8.50'.
ELSEIF iv_weight <= 10.
rv_rate = '12.00'.
ELSEIF iv_weight <= {{p}}if_shipping_cost=>c_rate-bulk_weight.
rv_rate = '18.75'.
ELSE.
DATA(lv_started_kg) = CONV i(
ceil( iv_weight - {{p}}if_shipping_cost=>c_rate-bulk_weight ) ).
rv_rate = '18.75' + lv_started_kg * {{p}}if_shipping_cost=>c_rate-bulk_step.
ENDIF.
ENDMETHOD.
METHOD express_surcharge.
rv_surcharge = round( val = iv_rate * {{p}}if_shipping_cost=>c_rate-express_percent / 100
dec = 2 ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,68 @@
CLASS ltc_shipping_cost_calc DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}if_shipping_cost.
METHODS setup.
METHODS zero_weight_is_free FOR TESTING.
METHODS minimum_charge FOR TESTING.
METHODS tier_limits FOR TESTING.
METHODS bulk_weight FOR TESTING.
METHODS express_surcharge FOR TESTING.
METHODS express_below_minimum FOR TESTING.
METHODS assert_cost
IMPORTING iv_weight TYPE {{p}}if_shipping_cost=>ty_weight
iv_express TYPE abap_bool
iv_exp TYPE {{p}}if_shipping_cost=>ty_amount
iv_msg TYPE string.
ENDCLASS.
CLASS ltc_shipping_cost_calc IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}shipping_cost_calc( ).
ENDMETHOD.
METHOD assert_cost.
cl_abap_unit_assert=>assert_equals(
exp = iv_exp
act = mo_cut->calculate( iv_weight = iv_weight iv_express = iv_express )
msg = iv_msg ).
ENDMETHOD.
METHOD zero_weight_is_free.
assert_cost( iv_weight = '0.000' iv_express = abap_true
iv_exp = '0.00' iv_msg = `zero weight` ).
ENDMETHOD.
METHOD minimum_charge.
assert_cost( iv_weight = '0.500' iv_express = abap_false
iv_exp = '6.00' iv_msg = `minimum charge` ).
ENDMETHOD.
METHOD tier_limits.
assert_cost( iv_weight = '1.000' iv_express = abap_false
iv_exp = '6.00' iv_msg = `1.000 kg` ).
assert_cost( iv_weight = '1.001' iv_express = abap_false
iv_exp = '8.50' iv_msg = `1.001 kg` ).
ENDMETHOD.
METHOD bulk_weight.
assert_cost( iv_weight = '31.500' iv_express = abap_false
iv_exp = '18.75' iv_msg = `31.500 kg` ).
assert_cost( iv_weight = '32.000' iv_express = abap_false
iv_exp = '19.40' iv_msg = `32.000 kg` ).
ENDMETHOD.
METHOD express_surcharge.
assert_cost( iv_weight = '2.000' iv_express = abap_true
iv_exp = '11.48' iv_msg = `2.000 kg express` ).
ENDMETHOD.
METHOD express_below_minimum.
assert_cost( iv_weight = '0.500' iv_express = abap_true
iv_exp = '6.62' iv_msg = `0.500 kg express` ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,17 @@
INTERFACE {{p}}if_shipping_cost PUBLIC.
TYPES ty_weight TYPE p LENGTH 13 DECIMALS 3.
TYPES ty_amount TYPE p LENGTH 15 DECIMALS 2.
CONSTANTS:
BEGIN OF c_rate,
min_charge TYPE ty_amount VALUE '6.00',
express_percent TYPE i VALUE 35,
bulk_weight TYPE ty_weight VALUE '31.500',
bulk_step TYPE ty_amount VALUE '0.65',
END OF c_rate.
METHODS calculate
IMPORTING iv_weight TYPE ty_weight
iv_express TYPE abap_bool
RETURNING VALUE(rv_cost) TYPE ty_amount.
ENDINTERFACE.

View File

@@ -0,0 +1,53 @@
CLASS {{p}}shipping_cost_calc DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_shipping_cost.
PRIVATE SECTION.
METHODS base_rate
IMPORTING iv_weight TYPE {{p}}if_shipping_cost=>ty_weight
RETURNING VALUE(rv_rate) TYPE {{p}}if_shipping_cost=>ty_amount.
METHODS express_surcharge
IMPORTING iv_rate TYPE {{p}}if_shipping_cost=>ty_amount
RETURNING VALUE(rv_surcharge) TYPE {{p}}if_shipping_cost=>ty_amount.
ENDCLASS.
CLASS {{p}}shipping_cost_calc IMPLEMENTATION.
METHOD {{p}}if_shipping_cost~calculate.
IF iv_weight <= 0.
RETURN.
ENDIF.
rv_cost = base_rate( iv_weight ).
IF rv_cost < {{p}}if_shipping_cost=>c_rate-min_charge.
rv_cost = {{p}}if_shipping_cost=>c_rate-min_charge.
ENDIF.
IF iv_express = abap_true.
rv_cost = rv_cost + express_surcharge( rv_cost ).
ENDIF.
ENDMETHOD.
METHOD base_rate.
IF iv_weight < 1.
rv_rate = '4.90'.
ELSEIF iv_weight < 5.
rv_rate = '8.50'.
ELSEIF iv_weight < 10.
rv_rate = '12.00'.
ELSEIF iv_weight < {{p}}if_shipping_cost=>c_rate-bulk_weight.
rv_rate = '18.75'.
ELSE.
DATA lv_started_kg TYPE i.
lv_started_kg = iv_weight - {{p}}if_shipping_cost=>c_rate-bulk_weight.
rv_rate = '18.75' + lv_started_kg * {{p}}if_shipping_cost=>c_rate-bulk_step.
ENDIF.
ENDMETHOD.
METHOD express_surcharge.
rv_surcharge = round( val = iv_rate * {{p}}if_shipping_cost=>c_rate-express_percent / 100
dec = 2 ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,53 @@
CLASS {{p}}shipping_cost_legacy DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_shipping_cost.
PRIVATE SECTION.
METHODS base_rate
IMPORTING iv_weight TYPE {{p}}if_shipping_cost=>ty_weight
RETURNING VALUE(rv_rate) TYPE {{p}}if_shipping_cost=>ty_amount.
METHODS express_surcharge
IMPORTING iv_rate TYPE {{p}}if_shipping_cost=>ty_amount
RETURNING VALUE(rv_surcharge) TYPE {{p}}if_shipping_cost=>ty_amount.
ENDCLASS.
CLASS {{p}}shipping_cost_legacy IMPLEMENTATION.
METHOD {{p}}if_shipping_cost~calculate.
IF iv_weight <= 0.
RETURN.
ENDIF.
rv_cost = base_rate( iv_weight ).
IF rv_cost < {{p}}if_shipping_cost=>c_rate-min_charge.
rv_cost = {{p}}if_shipping_cost=>c_rate-min_charge.
ENDIF.
IF iv_express = abap_true.
rv_cost = rv_cost + express_surcharge( rv_cost ).
ENDIF.
ENDMETHOD.
METHOD base_rate.
IF iv_weight < 1.
rv_rate = '4.90'.
ELSEIF iv_weight < 5.
rv_rate = '8.50'.
ELSEIF iv_weight < 10.
rv_rate = '12.00'.
ELSEIF iv_weight < {{p}}if_shipping_cost=>c_rate-bulk_weight.
rv_rate = '18.75'.
ELSE.
DATA lv_started_kg TYPE i.
lv_started_kg = iv_weight - {{p}}if_shipping_cost=>c_rate-bulk_weight.
rv_rate = '18.75' + lv_started_kg * {{p}}if_shipping_cost=>c_rate-bulk_step.
ENDIF.
ENDMETHOD.
METHOD express_surcharge.
rv_surcharge = round( val = iv_rate * {{p}}if_shipping_cost=>c_rate-express_percent / 100
dec = 2 ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,63 @@
# 1. Goal
Calculate the shipping cost of a parcel. A webshop calls the calculation before
it shows the total price to the customer. The cost depends on the weight of the
parcel and on an express flag. The class that the webshop uses today returns
wrong costs. A corrected class must replace it.
# 2. Open questions
None.
# 3. Context
- The interface {{P}}IF_SHIPPING_COST exists in package $TMP. It is active.
- The interface contains the types TY_WEIGHT (packed, 3 decimals) and TY_AMOUNT
(packed, 2 decimals), the constant structure C_RATE, and one method:
CALCULATE IMPORTING iv_weight TYPE ty_weight, iv_express TYPE abap_bool
RETURNING VALUE(rv_cost) TYPE ty_amount.
- C_RATE has the components MIN_CHARGE (6.00), EXPRESS_PERCENT (35),
BULK_WEIGHT (31.500), and BULK_STEP (0.65).
- The class {{P}}SHIPPING_COST_LEGACY exists in package $TMP. It is active and
it implements the interface, but the calculated costs are wrong.
- Reported symptoms of {{P}}SHIPPING_COST_LEGACY:
1. A parcel with a weight that is exactly on a weight limit is charged with
the rate of the next weight limit.
2. A parcel that is heavier than C_RATE-BULK_WEIGHT is charged as if its
weight were exactly C_RATE-BULK_WEIGHT.
3. An express parcel that is lighter than the minimum charge gets a total
that is too high.
# 4. Contract
- Create the class {{P}}SHIPPING_COST_CALC in package $TMP.
- The class is public and final and has a public constructor without parameters.
- The class implements {{P}}IF_SHIPPING_COST.
- Do not add other public methods.
- Do not change {{P}}IF_SHIPPING_COST and {{P}}SHIPPING_COST_LEGACY.
# 5. Business rules
Apply the rules in this sequence.
1. If iv_weight is zero or negative, the cost is 0. Stop.
2. Else calculate the base rate from the weight:
- weight up to and including 1.000 kg: 4.90
- weight up to and including 5.000 kg: 8.50
- weight up to and including 10.000 kg: 12.00
- weight up to and including 31.500 kg: 18.75
- weight above 31.500 kg: 18.75 plus 0.65 for each started kilogram above
31.500 kg. A started kilogram is a kilogram that is not complete.
Example: 31.501 kg is 0.001 kg above 31.500 kg, so there is one started
kilogram. 33.000 kg is 1.500 kg above 31.500 kg, so there are two started
kilograms.
3. If iv_express is abap_true, add 35 percent of the base rate to the base rate.
Round this surcharge to 2 decimals. Round a half away from zero.
4. If the result is below 6.00, the result is 6.00.
5. The result has 2 decimals.
# 6. Constraints
- Release target: 8.16.
- Coding standards: Clean ABAP. Methods below 40 statements. No global variables.
No comment that restates the code.
- Out of scope: do not change {{P}}IF_SHIPPING_COST and
{{P}}SHIPPING_COST_LEGACY.
# 7. Acceptance
- The class is active and has no syntax error.
- The hidden tests pass.
- Write your own ABAP Unit tests for the class.

View File

@@ -0,0 +1,59 @@
{
"id": "G0009",
"category": "I",
"object_type": "CLAS",
"difficulty": 2,
"release_target": "v816",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 50,
"max_activations": 12
},
"seed": [
{
"type": "INTF",
"name": "{{P}}IF_SHIPPING_COST",
"file": "seed/if_shipping_cost.intf.abap",
"description": "Shipping cost contract with the weight and amount types, the rate constants and the CALCULATE method"
},
{
"type": "CLAS",
"name": "{{P}}SHIPPING_COST_LEGACY",
"file": "seed/shipping_cost_legacy.clas.abap",
"description": "Active class that implements the interface but calculates wrong costs"
}
],
"contract": [
{
"type": "CLAS",
"name": "{{P}}SHIPPING_COST_CALC",
"implements": "{{P}}IF_SHIPPING_COST"
}
],
"out_of_scope": [
"{{P}}IF_SHIPPING_COST",
"{{P}}SHIPPING_COST_LEGACY"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T02_HIDDEN",
"file": "hidden/t02_hidden.clas.abap",
"description": "T02 hidden tests for the shipping cost calculation"
}
],
"reference": [
{
"type": "CLAS",
"name": "{{P}}SHIPPING_COST_CALC",
"file": "reference/shipping_cost_calc.clas.abap",
"description": "Correct shipping cost calculation",
"testclasses_file": "reference/shipping_cost_calc.testclasses.abap"
}
],
"craft_checks": [
"method_length",
"cyclomatic_complexity",
"no_global_variables"
]
}

View File

@@ -0,0 +1,25 @@
{
"id": "G0010",
"pool": "eval",
"object_type": "CLAS",
"category": "C",
"attempts": [
{
"stage": "validate",
"oracle": 0,
"null": 0
},
{
"stage": "bundle",
"errors": [
"invalid JSON: Extra data: line 1 column 24591 (char 24590)"
]
},
{
"stage": "validate",
"oracle": 100.0,
"null": 0
}
],
"accepted": true
}

View File

@@ -0,0 +1,173 @@
CLASS {{p}}t02_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}if_stock_cover.
METHODS setup.
METHODS all_ok FOR TESTING.
METHODS shortage_is_reported FOR TESTING.
METHODS stock_used_in_order FOR TESTING.
METHODS negative_stock_ignored FOR TESTING.
METHODS zero_requirement_invalid FOR TESTING.
METHODS negative_req_invalid FOR TESTING.
METHODS missing_stock_is_short FOR TESTING.
METHODS case_and_blanks_ignored FOR TESTING.
METHODS stock_items_are_summed FOR TESTING.
METHODS no_requirements FOR TESTING.
METHODS shortages_filter_sort FOR TESTING.
METHODS shortages_empty_all_ok FOR TESTING.
ENDCLASS.
CLASS {{p}}t02_hidden IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}stock_cover_checker( ).
ENDMETHOD.
METHOD all_ok.
DATA(lt_stock) = VALUE {{p}}if_stock_cover=>tt_stock_item(
( material = 'MAT-A' plant = 'P1' quantity = 100 )
( material = 'MAT-B' plant = 'P1' quantity = 50 ) ).
DATA(lt_req) = VALUE {{p}}if_stock_cover=>tt_requirement(
( material = 'MAT-A' plant = 'P1' required = 30 )
( material = 'MAT-B' plant = 'P1' required = 50 ) ).
DATA(lt_exp) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-A' plant = 'P1' required = 30 available = 100 shortage = 0 status = 'OK' )
( material = 'MAT-B' plant = 'P1' required = 50 available = 50 shortage = 0 status = 'OK' ) ).
cl_abap_unit_assert=>assert_equals(
exp = lt_exp
act = mo_cut->check( it_stock = lt_stock it_requirement = lt_req ) ).
ENDMETHOD.
METHOD shortage_is_reported.
DATA(lt_stock) = VALUE {{p}}if_stock_cover=>tt_stock_item(
( material = 'MAT-A' plant = 'P1' quantity = 30 ) ).
DATA(lt_req) = VALUE {{p}}if_stock_cover=>tt_requirement(
( material = 'MAT-A' plant = 'P1' required = 50 ) ).
DATA(lt_exp) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-A' plant = 'P1' required = 50 available = 30 shortage = 20 status = 'SHORT' ) ).
cl_abap_unit_assert=>assert_equals(
exp = lt_exp
act = mo_cut->check( it_stock = lt_stock it_requirement = lt_req ) ).
ENDMETHOD.
METHOD stock_used_in_order.
DATA(lt_stock) = VALUE {{p}}if_stock_cover=>tt_stock_item(
( material = 'MAT-A' plant = 'P1' quantity = 100 ) ).
DATA(lt_req) = VALUE {{p}}if_stock_cover=>tt_requirement(
( material = 'MAT-A' plant = 'P1' required = 60 )
( material = 'MAT-A' plant = 'P1' required = 60 ) ).
DATA(lt_exp) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-A' plant = 'P1' required = 60 available = 100 shortage = 0 status = 'OK' )
( material = 'MAT-A' plant = 'P1' required = 60 available = 40 shortage = 20 status = 'SHORT' ) ).
cl_abap_unit_assert=>assert_equals(
exp = lt_exp
act = mo_cut->check( it_stock = lt_stock it_requirement = lt_req ) ).
ENDMETHOD.
METHOD negative_stock_ignored.
DATA(lt_stock) = VALUE {{p}}if_stock_cover=>tt_stock_item(
( material = 'MAT-A' plant = 'P1' quantity = 100 )
( material = 'MAT-A' plant = 'P1' quantity = -40 ) ).
DATA(lt_req) = VALUE {{p}}if_stock_cover=>tt_requirement(
( material = 'MAT-A' plant = 'P1' required = 100 ) ).
DATA(lt_exp) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-A' plant = 'P1' required = 100 available = 100 shortage = 0 status = 'OK' ) ).
cl_abap_unit_assert=>assert_equals(
exp = lt_exp
act = mo_cut->check( it_stock = lt_stock it_requirement = lt_req ) ).
ENDMETHOD.
METHOD zero_requirement_invalid.
DATA(lt_stock) = VALUE {{p}}if_stock_cover=>tt_stock_item(
( material = 'MAT-A' plant = 'P1' quantity = 50 ) ).
DATA(lt_req) = VALUE {{p}}if_stock_cover=>tt_requirement(
( material = 'MAT-A' plant = 'P1' required = 0 )
( material = 'MAT-A' plant = 'P1' required = 50 ) ).
DATA(lt_exp) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-A' plant = 'P1' required = 0 available = 50 shortage = 0 status = 'INVALID' )
( material = 'MAT-A' plant = 'P1' required = 50 available = 50 shortage = 0 status = 'OK' ) ).
cl_abap_unit_assert=>assert_equals(
exp = lt_exp
act = mo_cut->check( it_stock = lt_stock it_requirement = lt_req ) ).
ENDMETHOD.
METHOD negative_req_invalid.
DATA(lt_stock) = VALUE {{p}}if_stock_cover=>tt_stock_item(
( material = 'MAT-A' plant = 'P1' quantity = 50 ) ).
DATA(lt_req) = VALUE {{p}}if_stock_cover=>tt_requirement(
( material = 'MAT-A' plant = 'P1' required = -10 ) ).
DATA(lt_exp) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-A' plant = 'P1' required = -10 available = 50 shortage = 0 status = 'INVALID' ) ).
cl_abap_unit_assert=>assert_equals(
exp = lt_exp
act = mo_cut->check( it_stock = lt_stock it_requirement = lt_req ) ).
ENDMETHOD.
METHOD missing_stock_is_short.
DATA(lt_stock) = VALUE {{p}}if_stock_cover=>tt_stock_item( ).
DATA(lt_req) = VALUE {{p}}if_stock_cover=>tt_requirement(
( material = 'MAT-X' plant = 'P9' required = 25 ) ).
DATA(lt_exp) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-X' plant = 'P9' required = 25 available = 0 shortage = 25 status = 'SHORT' ) ).
cl_abap_unit_assert=>assert_equals(
exp = lt_exp
act = mo_cut->check( it_stock = lt_stock it_requirement = lt_req ) ).
ENDMETHOD.
METHOD case_and_blanks_ignored.
DATA(lt_stock) = VALUE {{p}}if_stock_cover=>tt_stock_item(
( material = 'mat-a' plant = 'p1' quantity = 80 ) ).
DATA(lt_req) = VALUE {{p}}if_stock_cover=>tt_requirement(
( material = ' MAT-A ' plant = 'P1 ' required = 80 ) ).
DATA(lt_exp) = VALUE {{p}}if_stock_cover=>tt_result(
( material = ' MAT-A ' plant = 'P1 ' required = 80 available = 80 shortage = 0 status = 'OK' ) ).
cl_abap_unit_assert=>assert_equals(
exp = lt_exp
act = mo_cut->check( it_stock = lt_stock it_requirement = lt_req ) ).
ENDMETHOD.
METHOD stock_items_are_summed.
DATA(lt_stock) = VALUE {{p}}if_stock_cover=>tt_stock_item(
( material = 'MAT-A' plant = 'P1' quantity = 10 )
( material = 'MAT-A' plant = 'P1' quantity = 20 )
( material = 'MAT-A' plant = 'P2' quantity = 5 ) ).
DATA(lt_req) = VALUE {{p}}if_stock_cover=>tt_requirement(
( material = 'MAT-A' plant = 'P1' required = 30 )
( material = 'MAT-A' plant = 'P2' required = 5 ) ).
DATA(lt_exp) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-A' plant = 'P1' required = 30 available = 30 shortage = 0 status = 'OK' )
( material = 'MAT-A' plant = 'P2' required = 5 available = 5 shortage = 0 status = 'OK' ) ).
cl_abap_unit_assert=>assert_equals(
exp = lt_exp
act = mo_cut->check( it_stock = lt_stock it_requirement = lt_req ) ).
ENDMETHOD.
METHOD no_requirements.
DATA(lt_stock) = VALUE {{p}}if_stock_cover=>tt_stock_item(
( material = 'MAT-A' plant = 'P1' quantity = 10 ) ).
DATA(lt_req) = VALUE {{p}}if_stock_cover=>tt_requirement( ).
DATA(lt_act) = mo_cut->check( it_stock = lt_stock it_requirement = lt_req ).
cl_abap_unit_assert=>assert_equals( exp = 0 act = lines( lt_act ) ).
ENDMETHOD.
METHOD shortages_filter_sort.
DATA(lt_in) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-C' plant = 'P1' required = 10 available = 0 shortage = 10 status = 'SHORT' )
( material = 'MAT-A' plant = 'P2' required = 10 available = 0 shortage = 10 status = 'SHORT' )
( material = 'MAT-B' plant = 'P1' required = 10 available = 10 shortage = 0 status = 'OK' )
( material = 'MAT-A' plant = 'P1' required = 10 available = 0 shortage = 10 status = 'SHORT' )
( material = 'MAT-D' plant = 'P1' required = 10 available = 10 shortage = 0 status = 'INVALID' ) ).
DATA(lt_exp) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-A' plant = 'P1' required = 10 available = 0 shortage = 10 status = 'SHORT' )
( material = 'MAT-A' plant = 'P2' required = 10 available = 0 shortage = 10 status = 'SHORT' )
( material = 'MAT-C' plant = 'P1' required = 10 available = 0 shortage = 10 status = 'SHORT' ) ).
cl_abap_unit_assert=>assert_equals( exp = lt_exp act = mo_cut->shortages( lt_in ) ).
ENDMETHOD.
METHOD shortages_empty_all_ok.
DATA(lt_in) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-A' plant = 'P1' required = 10 available = 10 shortage = 0 status = 'OK' )
( material = 'MAT-B' plant = 'P1' required = 10 available = 10 shortage = 0 status = 'INVALID' ) ).
cl_abap_unit_assert=>assert_equals( exp = 0 act = lines( mo_cut->shortages( lt_in ) ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,128 @@
CLASS {{p}}stock_cover_checker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES {{p}}if_stock_cover.
PRIVATE SECTION.
TYPES:
BEGIN OF ty_stock,
material TYPE {{p}}if_stock_cover=>ty_material,
plant TYPE {{p}}if_stock_cover=>ty_plant,
remaining TYPE {{p}}if_stock_cover=>ty_quantity,
END OF ty_stock.
TYPES tt_stock TYPE HASHED TABLE OF ty_stock WITH UNIQUE KEY material plant.
METHODS aggregate
IMPORTING it_stock TYPE {{p}}if_stock_cover=>tt_stock_item
RETURNING VALUE(rt_stock) TYPE tt_stock.
METHODS free_quantity
IMPORTING it_stock TYPE tt_stock
iv_material TYPE {{p}}if_stock_cover=>ty_material
iv_plant TYPE {{p}}if_stock_cover=>ty_plant
RETURNING VALUE(rv_quantity) TYPE {{p}}if_stock_cover=>ty_quantity.
METHODS consume
IMPORTING iv_material TYPE {{p}}if_stock_cover=>ty_material
iv_plant TYPE {{p}}if_stock_cover=>ty_plant
iv_quantity TYPE {{p}}if_stock_cover=>ty_quantity
CHANGING ct_stock TYPE tt_stock.
METHODS normalize_material
IMPORTING iv_material TYPE {{p}}if_stock_cover=>ty_material
RETURNING VALUE(rv_material) TYPE {{p}}if_stock_cover=>ty_material.
METHODS normalize_plant
IMPORTING iv_plant TYPE {{p}}if_stock_cover=>ty_plant
RETURNING VALUE(rv_plant) TYPE {{p}}if_stock_cover=>ty_plant.
ENDCLASS.
CLASS {{p}}stock_cover_checker IMPLEMENTATION.
METHOD {{p}}if_stock_cover~check.
DATA(lt_stock) = aggregate( it_stock ).
LOOP AT it_requirement INTO DATA(ls_requirement).
DATA(lv_free) = free_quantity( it_stock = lt_stock
iv_material = ls_requirement-material
iv_plant = ls_requirement-plant ).
DATA(ls_result) = VALUE {{p}}if_stock_cover=>ty_result(
material = ls_requirement-material
plant = ls_requirement-plant
required = ls_requirement-required
available = lv_free ).
IF ls_requirement-required <= 0.
ls_result-status = {{p}}if_stock_cover=>c_status-invalid.
ELSEIF lv_free >= ls_requirement-required.
ls_result-status = {{p}}if_stock_cover=>c_status-ok.
consume( EXPORTING iv_material = ls_requirement-material
iv_plant = ls_requirement-plant
iv_quantity = ls_requirement-required
CHANGING ct_stock = lt_stock ).
ELSE.
ls_result-status = {{p}}if_stock_cover=>c_status-short.
ls_result-shortage = ls_requirement-required - lv_free.
consume( EXPORTING iv_material = ls_requirement-material
iv_plant = ls_requirement-plant
iv_quantity = lv_free
CHANGING ct_stock = lt_stock ).
ENDIF.
APPEND ls_result TO rt_result.
ENDLOOP.
ENDMETHOD.
METHOD {{p}}if_stock_cover~shortages.
rt_shortage = it_result.
DELETE rt_shortage WHERE status <> {{p}}if_stock_cover=>c_status-short.
SORT rt_shortage BY material plant.
ENDMETHOD.
METHOD aggregate.
LOOP AT it_stock INTO DATA(ls_stock).
IF ls_stock-quantity < 0.
CONTINUE.
ENDIF.
DATA(lv_material) = normalize_material( ls_stock-material ).
DATA(lv_plant) = normalize_plant( ls_stock-plant ).
ASSIGN rt_stock[ material = lv_material plant = lv_plant ] TO FIELD-SYMBOL(<ls_stock_sum>).
IF sy-subrc = 0.
<ls_stock_sum>-remaining = <ls_stock_sum>-remaining + ls_stock-quantity.
ELSE.
INSERT VALUE #( material = lv_material
plant = lv_plant
remaining = ls_stock-quantity ) INTO TABLE rt_stock.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD free_quantity.
ASSIGN it_stock[ material = normalize_material( iv_material )
plant = normalize_plant( iv_plant ) ] TO FIELD-SYMBOL(<ls_stock>).
IF sy-subrc = 0.
rv_quantity = <ls_stock>-remaining.
ENDIF.
ENDMETHOD.
METHOD consume.
ASSIGN ct_stock[ material = normalize_material( iv_material )
plant = normalize_plant( iv_plant ) ] TO FIELD-SYMBOL(<ls_stock>).
IF sy-subrc = 0.
<ls_stock>-remaining = <ls_stock>-remaining - iv_quantity.
ENDIF.
ENDMETHOD.
METHOD normalize_material.
DATA lv_text TYPE string.
lv_text = iv_material.
REPLACE ALL OCCURRENCES OF ` ` IN lv_text WITH ``.
rv_material = to_upper( lv_text ).
ENDMETHOD.
METHOD normalize_plant.
DATA lv_text TYPE string.
lv_text = iv_plant.
REPLACE ALL OCCURRENCES OF ` ` IN lv_text WITH ``.
rv_plant = to_upper( lv_text ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,93 @@
CLASS ltc_stock_cover DEFINITION FINAL FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_cut TYPE REF TO {{p}}if_stock_cover.
METHODS setup.
METHODS covered_req_is_ok FOR TESTING.
METHODS shortage_is_reported FOR TESTING.
METHODS stock_shared_in_order FOR TESTING.
METHODS invalid_req_keeps_stock FOR TESTING.
METHODS blanks_and_case_ignored FOR TESTING.
METHODS shortages_are_sorted FOR TESTING.
ENDCLASS.
CLASS ltc_stock_cover IMPLEMENTATION.
METHOD setup.
mo_cut = NEW {{p}}stock_cover_checker( ).
ENDMETHOD.
METHOD covered_req_is_ok.
DATA(lt_stock) = VALUE {{p}}if_stock_cover=>tt_stock_item(
( material = 'MAT-A' plant = 'P1' quantity = 40 ) ).
DATA(lt_req) = VALUE {{p}}if_stock_cover=>tt_requirement(
( material = 'MAT-A' plant = 'P1' required = 40 ) ).
DATA(lt_exp) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-A' plant = 'P1' required = 40 available = 40 shortage = 0 status = 'OK' ) ).
cl_abap_unit_assert=>assert_equals(
exp = lt_exp
act = mo_cut->check( it_stock = lt_stock it_requirement = lt_req ) ).
ENDMETHOD.
METHOD shortage_is_reported.
DATA(lt_stock) = VALUE {{p}}if_stock_cover=>tt_stock_item(
( material = 'MAT-A' plant = 'P1' quantity = 5 ) ).
DATA(lt_req) = VALUE {{p}}if_stock_cover=>tt_requirement(
( material = 'MAT-A' plant = 'P1' required = 12 ) ).
DATA(lt_exp) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-A' plant = 'P1' required = 12 available = 5 shortage = 7 status = 'SHORT' ) ).
cl_abap_unit_assert=>assert_equals(
exp = lt_exp
act = mo_cut->check( it_stock = lt_stock it_requirement = lt_req ) ).
ENDMETHOD.
METHOD stock_shared_in_order.
DATA(lt_stock) = VALUE {{p}}if_stock_cover=>tt_stock_item(
( material = 'MAT-A' plant = 'P1' quantity = 10 ) ).
DATA(lt_req) = VALUE {{p}}if_stock_cover=>tt_requirement(
( material = 'MAT-A' plant = 'P1' required = 6 )
( material = 'MAT-A' plant = 'P1' required = 6 ) ).
DATA(lt_exp) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-A' plant = 'P1' required = 6 available = 10 shortage = 0 status = 'OK' )
( material = 'MAT-A' plant = 'P1' required = 6 available = 4 shortage = 2 status = 'SHORT' ) ).
cl_abap_unit_assert=>assert_equals(
exp = lt_exp
act = mo_cut->check( it_stock = lt_stock it_requirement = lt_req ) ).
ENDMETHOD.
METHOD invalid_req_keeps_stock.
DATA(lt_stock) = VALUE {{p}}if_stock_cover=>tt_stock_item(
( material = 'MAT-A' plant = 'P1' quantity = 9 ) ).
DATA(lt_req) = VALUE {{p}}if_stock_cover=>tt_requirement(
( material = 'MAT-A' plant = 'P1' required = 0 )
( material = 'MAT-A' plant = 'P1' required = 9 ) ).
DATA(lt_exp) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-A' plant = 'P1' required = 0 available = 9 shortage = 0 status = 'INVALID' )
( material = 'MAT-A' plant = 'P1' required = 9 available = 9 shortage = 0 status = 'OK' ) ).
cl_abap_unit_assert=>assert_equals(
exp = lt_exp
act = mo_cut->check( it_stock = lt_stock it_requirement = lt_req ) ).
ENDMETHOD.
METHOD blanks_and_case_ignored.
DATA(lt_stock) = VALUE {{p}}if_stock_cover=>tt_stock_item(
( material = 'mat-a' plant = 'p1' quantity = 7 ) ).
DATA(lt_req) = VALUE {{p}}if_stock_cover=>tt_requirement(
( material = ' MAT-A ' plant = 'P1' required = 7 ) ).
DATA(lt_exp) = VALUE {{p}}if_stock_cover=>tt_result(
( material = ' MAT-A ' plant = 'P1' required = 7 available = 7 shortage = 0 status = 'OK' ) ).
cl_abap_unit_assert=>assert_equals(
exp = lt_exp
act = mo_cut->check( it_stock = lt_stock it_requirement = lt_req ) ).
ENDMETHOD.
METHOD shortages_are_sorted.
DATA(lt_in) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-B' plant = 'P1' required = 1 available = 0 shortage = 1 status = 'SHORT' )
( material = 'MAT-A' plant = 'P2' required = 1 available = 0 shortage = 1 status = 'SHORT' )
( material = 'MAT-A' plant = 'P1' required = 1 available = 1 shortage = 0 status = 'OK' ) ).
DATA(lt_exp) = VALUE {{p}}if_stock_cover=>tt_result(
( material = 'MAT-A' plant = 'P2' required = 1 available = 0 shortage = 1 status = 'SHORT' )
( material = 'MAT-B' plant = 'P1' required = 1 available = 0 shortage = 1 status = 'SHORT' ) ).
cl_abap_unit_assert=>assert_equals( exp = lt_exp act = mo_cut->shortages( lt_in ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,46 @@
INTERFACE {{p}}if_stock_cover PUBLIC.
TYPES ty_material TYPE c LENGTH 18.
TYPES ty_plant TYPE c LENGTH 4.
TYPES ty_quantity TYPE p LENGTH 13 DECIMALS 3.
TYPES ty_status TYPE c LENGTH 10.
TYPES: BEGIN OF ty_stock_item,
material TYPE ty_material,
plant TYPE ty_plant,
quantity TYPE ty_quantity,
END OF ty_stock_item.
TYPES tt_stock_item TYPE STANDARD TABLE OF ty_stock_item WITH EMPTY KEY.
TYPES: BEGIN OF ty_requirement,
material TYPE ty_material,
plant TYPE ty_plant,
required TYPE ty_quantity,
END OF ty_requirement.
TYPES tt_requirement TYPE STANDARD TABLE OF ty_requirement WITH EMPTY KEY.
TYPES: BEGIN OF ty_result,
material TYPE ty_material,
plant TYPE ty_plant,
required TYPE ty_quantity,
available TYPE ty_quantity,
shortage TYPE ty_quantity,
status TYPE ty_status,
END OF ty_result.
TYPES tt_result TYPE STANDARD TABLE OF ty_result WITH EMPTY KEY.
CONSTANTS:
BEGIN OF c_status,
ok TYPE ty_status VALUE 'OK',
short TYPE ty_status VALUE 'SHORT',
invalid TYPE ty_status VALUE 'INVALID',
END OF c_status.
METHODS check
IMPORTING it_stock TYPE tt_stock_item
it_requirement TYPE tt_requirement
RETURNING VALUE(rt_result) TYPE tt_result.
METHODS shortages
IMPORTING it_result TYPE tt_result
RETURNING VALUE(rt_shortage) TYPE tt_result.
ENDINTERFACE.

View File

@@ -0,0 +1,70 @@
# 1. Goal
A warehouse planner checks if the free stock covers the requirements of a
production run. The planner sends a list of stock items and a list of
requirements to a class. The class returns one result row for each requirement.
# 2. Open questions
None.
# 3. Context
- The interface {{P}}IF_STOCK_COVER exists in package $TMP. It is active.
- The interface contains the types TY_MATERIAL (CHAR 18), TY_PLANT (CHAR 4),
TY_QUANTITY (PACKED 13, 3) and TY_STATUS (CHAR 10).
- The interface contains the structures TY_STOCK_ITEM, TY_REQUIREMENT and
TY_RESULT and the table types TT_STOCK_ITEM, TT_REQUIREMENT and TT_RESULT.
- The interface contains the constant structure C_STATUS with the values OK,
SHORT and INVALID.
- The interface has the method CHECK. It imports a stock table and a requirement
table and returns a result table.
- The interface has the method SHORTAGES. It imports a result table and returns a
result table.
# 4. Contract
- Create the class {{P}}STOCK_COVER_CHECKER in package $TMP.
- The class is public and final.
- The class implements {{P}}IF_STOCK_COVER.
- Do not add other public methods.
- The hidden tests create the class with NEW {{P}}STOCK_COVER_CHECKER( ).
# 5. Business rules
## Stock
1. Each stock item has a material, a plant and a quantity. Ignore every stock
item whose quantity is less than zero.
2. Sum the quantities of the remaining stock items for each combination of
material and plant. This sum is the free stock of the material and plant.
3. Compare material and plant without regard to case. Remove all blanks before
you compare them.
## Requirements
4. Process the requirements in the given sequence. The result contains one row
for each requirement in the same sequence.
5. The available quantity of a requirement is the free stock of its material and
plant at the time of the requirement. If no stock exists for the material and
plant, the available quantity is 0.
6. If the required quantity is less than or equal to 0, the status is INVALID.
The requirement does not use free stock. The shortage is 0.
7. Else if the available quantity is greater than or equal to the required
quantity, the status is OK. The shortage is 0. The requirement uses the
required quantity of the free stock.
8. Else the status is SHORT. The requirement uses all free stock. The shortage is
the required quantity minus the available quantity.
## Result
9. A result row contains the material and the plant of the requirement without a
change, the required quantity, the available quantity, the shortage and the
status.
10. The method SHORTAGES returns all rows of the result table whose status is
SHORT. Sort these rows by material in ascending order and then by plant in
ascending order. The result can be empty.
# 6. Constraints
- Release target: 8.16 (SAP_BASIS 816).
- Coding standards: Clean ABAP. Method length below 40 statements. No global
variables. No comment that restates the code.
- Out of scope: do not change {{P}}IF_STOCK_COVER.
# 7. Acceptance
- The class is active and has no syntax error.
- The hidden tests pass.
- Write your own ABAP Unit tests for the class.

View File

@@ -0,0 +1,52 @@
{
"id": "G0010",
"category": "C",
"object_type": "CLAS",
"difficulty": 2,
"release_target": "v816",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 50,
"max_activations": 12
},
"seed": [
{
"type": "INTF",
"name": "{{P}}IF_STOCK_COVER",
"file": "seed/if_stock_cover.intf.abap",
"description": "Stock cover interface with types, constants and two methods"
}
],
"contract": [
{
"type": "CLAS",
"name": "{{P}}STOCK_COVER_CHECKER",
"implements": "{{P}}IF_STOCK_COVER"
}
],
"out_of_scope": [
"{{P}}IF_STOCK_COVER"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T02_HIDDEN",
"file": "hidden/t02_hidden.clas.abap",
"description": "T02 hidden tests for the stock cover checker"
}
],
"reference": [
{
"type": "CLAS",
"name": "{{P}}STOCK_COVER_CHECKER",
"file": "reference/stock_cover_checker.clas.abap",
"description": "Stock cover checker with hashed stock table",
"testclasses_file": "reference/stock_cover_checker.testclasses.abap"
}
],
"craft_checks": [
"table_kind",
"access_path",
"method_length"
]
}

View File

@@ -0,0 +1,29 @@
{
"id": "G0011",
"pool": "eval",
"object_type": "FUNC",
"category": "A",
"attempts": [
{
"stage": "validate",
"oracle": 0,
"null": 0
},
{
"stage": "validate",
"oracle": 92.5,
"null": 0
},
{
"stage": "validate",
"oracle": 0,
"null": 0
},
{
"stage": "validate",
"oracle": 92.5,
"null": 0
}
],
"accepted": false
}

View File

@@ -0,0 +1,117 @@
CLASS {{p}}t14_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS call
IMPORTING
iv_stock TYPE decfloat34
iv_reorder_point TYPE decfloat34
iv_target_stock TYPE decfloat34
iv_pack_size TYPE decfloat34
iv_min_qty TYPE decfloat34 DEFAULT 0
EXPORTING
ev_qty TYPE decfloat34
ev_subrc TYPE sysubrc.
METHODS no_replenishment_when_stock_ok FOR TESTING.
METHODS replenish_to_target FOR TESTING.
METHODS pack_rounding_up FOR TESTING.
METHODS exact_multiple_not_rounded FOR TESTING.
METHODS minimum_quantity_applied FOR TESTING.
METHODS minimum_quantity_rounded_up FOR TESTING.
METHODS negative_minimum_is_zero FOR TESTING.
METHODS target_below_stock_is_zero FOR TESTING.
METHODS invalid_pack_size FOR TESTING.
METHODS class_calculates_directly FOR TESTING.
ENDCLASS.
CLASS {{p}}t14_hidden IMPLEMENTATION.
METHOD call.
ev_qty = 0.
CALL FUNCTION '{{P}}REPL_CALC'
EXPORTING
iv_stock = iv_stock
iv_reorder_point = iv_reorder_point
iv_target_stock = iv_target_stock
iv_pack_size = iv_pack_size
iv_min_qty = iv_min_qty
IMPORTING
ev_qty = ev_qty
EXCEPTIONS
invalid_pack_size = 1
OTHERS = 2.
ev_subrc = sy-subrc.
ENDMETHOD.
METHOD no_replenishment_when_stock_ok.
DATA lv_qty TYPE decfloat34.
call( EXPORTING iv_stock = 50 iv_reorder_point = 20 iv_target_stock = 100 iv_pack_size = 10
IMPORTING ev_qty = lv_qty ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 0 ) act = lv_qty ).
ENDMETHOD.
METHOD replenish_to_target.
DATA lv_qty TYPE decfloat34.
call( EXPORTING iv_stock = 10 iv_reorder_point = 20 iv_target_stock = 50 iv_pack_size = 12
IMPORTING ev_qty = lv_qty ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 48 ) act = lv_qty ).
ENDMETHOD.
METHOD pack_rounding_up.
DATA lv_qty TYPE decfloat34.
call( EXPORTING iv_stock = 0 iv_reorder_point = 10 iv_target_stock = 25 iv_pack_size = 6
IMPORTING ev_qty = lv_qty ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 30 ) act = lv_qty ).
ENDMETHOD.
METHOD exact_multiple_not_rounded.
DATA lv_qty TYPE decfloat34.
call( EXPORTING iv_stock = 0 iv_reorder_point = 10 iv_target_stock = 24 iv_pack_size = 12
IMPORTING ev_qty = lv_qty ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 24 ) act = lv_qty ).
ENDMETHOD.
METHOD minimum_quantity_applied.
DATA lv_qty TYPE decfloat34.
call( EXPORTING iv_stock = 10 iv_reorder_point = 20 iv_target_stock = 12 iv_pack_size = 1 iv_min_qty = 7
IMPORTING ev_qty = lv_qty ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 7 ) act = lv_qty ).
ENDMETHOD.
METHOD minimum_quantity_rounded_up.
DATA lv_qty TYPE decfloat34.
call( EXPORTING iv_stock = 10 iv_reorder_point = 20 iv_target_stock = 12 iv_pack_size = 5 iv_min_qty = 7
IMPORTING ev_qty = lv_qty ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 10 ) act = lv_qty ).
ENDMETHOD.
METHOD negative_minimum_is_zero.
DATA lv_qty TYPE decfloat34.
call( EXPORTING iv_stock = 0 iv_reorder_point = 10 iv_target_stock = 10 iv_pack_size = 4 iv_min_qty = -5
IMPORTING ev_qty = lv_qty ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 12 ) act = lv_qty ).
ENDMETHOD.
METHOD target_below_stock_is_zero.
DATA lv_qty TYPE decfloat34.
call( EXPORTING iv_stock = 10 iv_reorder_point = 20 iv_target_stock = 5 iv_pack_size = 4
IMPORTING ev_qty = lv_qty ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 0 ) act = lv_qty ).
ENDMETHOD.
METHOD invalid_pack_size.
DATA lv_rc TYPE sysubrc.
call( EXPORTING iv_stock = 0 iv_reorder_point = 10 iv_target_stock = 20 iv_pack_size = 0
IMPORTING ev_subrc = lv_rc ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lv_rc ).
ENDMETHOD.
METHOD class_calculates_directly.
DATA(lv_qty) = NEW {{p}}cl_repl( )->calculate(
iv_stock = 10
iv_reorder_point = 20
iv_target_stock = 50
iv_pack_size = 12
iv_min_qty = 0 ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 48 ) act = lv_qty ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,38 @@
CLASS {{p}}cl_repl DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS calculate
IMPORTING
iv_stock TYPE decfloat34
iv_reorder_point TYPE decfloat34
iv_target_stock TYPE decfloat34
iv_pack_size TYPE decfloat34
iv_min_qty TYPE decfloat34
RETURNING
VALUE(rv_qty) TYPE decfloat34.
ENDCLASS.
CLASS {{p}}cl_repl IMPLEMENTATION.
METHOD calculate.
IF iv_stock >= iv_reorder_point.
rv_qty = 0.
RETURN.
ENDIF.
DATA(lv_need) = iv_target_stock - iv_stock.
DATA(lv_min_qty) = iv_min_qty.
IF lv_min_qty < 0.
lv_min_qty = 0.
ENDIF.
IF lv_need < lv_min_qty.
lv_need = lv_min_qty.
ENDIF.
IF lv_need <= 0.
rv_qty = 0.
RETURN.
ENDIF.
rv_qty = ceil( lv_need / iv_pack_size ) * iv_pack_size.
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,24 @@
FUNCTION {{p}}repl_calc
IMPORTING
iv_stock TYPE decfloat34
iv_reorder_point TYPE decfloat34
iv_target_stock TYPE decfloat34
iv_pack_size TYPE decfloat34
iv_min_qty TYPE decfloat34
EXPORTING
ev_qty TYPE decfloat34
EXCEPTIONS
invalid_pack_size.
IF iv_pack_size <= 0.
RAISE invalid_pack_size.
ENDIF.
ev_qty = NEW {{p}}cl_repl( )->calculate(
iv_stock = iv_stock
iv_reorder_point = iv_reorder_point
iv_target_stock = iv_target_stock
iv_pack_size = iv_pack_size
iv_min_qty = iv_min_qty ).
ENDFUNCTION.

View File

@@ -0,0 +1,100 @@
CLASS {{p}}t14_test DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS qty_for
IMPORTING
iv_stock TYPE decfloat34
iv_reorder_point TYPE decfloat34
iv_target_stock TYPE decfloat34
iv_pack_size TYPE decfloat34
iv_min_qty TYPE decfloat34 DEFAULT 0
RETURNING
VALUE(rv_qty) TYPE decfloat34.
METHODS replenish_to_target FOR TESTING.
METHODS no_replenishment FOR TESTING.
METHODS minimum_quantity FOR TESTING.
METHODS pack_rounding FOR TESTING.
METHODS invalid_pack_size FOR TESTING.
METHODS class_direct_call FOR TESTING.
METHODS class_no_replenishment FOR TESTING.
ENDCLASS.
CLASS {{p}}t14_test IMPLEMENTATION.
METHOD qty_for.
CALL FUNCTION '{{P}}REPL_CALC'
EXPORTING
iv_stock = iv_stock
iv_reorder_point = iv_reorder_point
iv_target_stock = iv_target_stock
iv_pack_size = iv_pack_size
iv_min_qty = iv_min_qty
IMPORTING
ev_qty = rv_qty
EXCEPTIONS
invalid_pack_size = 1
OTHERS = 2.
cl_abap_unit_assert=>assert_subrc( exp = 0 ).
ENDMETHOD.
METHOD replenish_to_target.
cl_abap_unit_assert=>assert_equals(
exp = CONV decfloat34( 48 )
act = qty_for( iv_stock = 10 iv_reorder_point = 20 iv_target_stock = 50 iv_pack_size = 12 ) ).
ENDMETHOD.
METHOD no_replenishment.
cl_abap_unit_assert=>assert_equals(
exp = CONV decfloat34( 0 )
act = qty_for( iv_stock = 30 iv_reorder_point = 20 iv_target_stock = 50 iv_pack_size = 12 ) ).
ENDMETHOD.
METHOD minimum_quantity.
cl_abap_unit_assert=>assert_equals(
exp = CONV decfloat34( 10 )
act = qty_for( iv_stock = 10 iv_reorder_point = 20 iv_target_stock = 12 iv_pack_size = 5 iv_min_qty = 7 ) ).
ENDMETHOD.
METHOD pack_rounding.
cl_abap_unit_assert=>assert_equals(
exp = CONV decfloat34( 30 )
act = qty_for( iv_stock = 0 iv_reorder_point = 10 iv_target_stock = 25 iv_pack_size = 6 ) ).
ENDMETHOD.
METHOD invalid_pack_size.
DATA lv_qty TYPE decfloat34.
CALL FUNCTION '{{P}}REPL_CALC'
EXPORTING
iv_stock = 0
iv_reorder_point = 10
iv_target_stock = 20
iv_pack_size = 0
iv_min_qty = 0
IMPORTING
ev_qty = lv_qty
EXCEPTIONS
invalid_pack_size = 1
OTHERS = 2.
cl_abap_unit_assert=>assert_equals( exp = 1 act = sy-subrc ).
ENDMETHOD.
METHOD class_direct_call.
DATA(lv_qty) = NEW {{p}}cl_repl( )->calculate(
iv_stock = 0
iv_reorder_point = 10
iv_target_stock = 24
iv_pack_size = 12
iv_min_qty = 0 ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 24 ) act = lv_qty ).
ENDMETHOD.
METHOD class_no_replenishment.
DATA(lv_qty) = NEW {{p}}cl_repl( )->calculate(
iv_stock = 40
iv_reorder_point = 20
iv_target_stock = 100
iv_pack_size = 10
iv_min_qty = 0 ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 0 ) act = lv_qty ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,9 @@
FUNCTION {{p}}repl_sample
IMPORTING
iv_bin TYPE string
EXPORTING
ev_info TYPE string.
ev_info = |Storage bin { iv_bin }|.
ENDFUNCTION.

View File

@@ -0,0 +1,66 @@
# 1. Goal
A warehouse system needs the quantity for a replenishment order of a storage
bin. The calling program calls the function module {{P}}REPL_CALC. The
calculation logic must be in the class {{P}}CL_REPL, because other programs
also use this logic.
# 2. Open questions
None.
# 3. Context
- Function group {{P}}FG_REPL exists in package $TMP.
- Function module {{P}}REPL_SAMPLE exists in this function group. It shows bin
information. Do not change it.
- The class {{P}}CL_REPL does not exist. Create it first.
# 4. Contract
## 4.1 Class {{P}}CL_REPL
- Package: $TMP.
- The class is instantiable without parameters.
- Public instance method calculate:
- importing iv_stock, type decfloat34
- importing iv_reorder_point, type decfloat34
- importing iv_target_stock, type decfloat34
- importing iv_pack_size, type decfloat34
- importing iv_min_qty, type decfloat34
- returning rv_qty, type decfloat34
- The method has no exception. Call it only with a positive pack size.
## 4.2 Function module {{P}}REPL_CALC
- Function group: {{P}}FG_REPL.
- Importing parameters:
- iv_stock, type decfloat34
- iv_reorder_point, type decfloat34
- iv_target_stock, type decfloat34
- iv_pack_size, type decfloat34
- iv_min_qty, type decfloat34
- Exporting parameters:
- ev_qty, type decfloat34
- Exceptions: invalid_pack_size.
- The function module {{P}}REPL_CALC must call the method calculate of the class
{{P}}CL_REPL. Create and activate the class before you activate the function
module.
# 5. Business rules
1. If the pack size is not positive, the function module raises the exception
INVALID_PACK_SIZE. The class method is not called in this case.
2. If the stock is greater than or equal to the reorder point, the
replenishment quantity is 0.
3. Otherwise, the requirement is the target stock minus the stock.
4. If the requirement is less than the minimum quantity, the requirement is the
minimum quantity. A minimum quantity below 0 counts as 0.
5. If the requirement is not positive, the replenishment quantity is 0.
6. Otherwise, round the requirement up to the next full multiple of the pack
size. The replenishment quantity is this multiple multiplied by the pack
size.
# 6. Constraints
- Release target: 7.58.
- Use Clean ABAP. Keep each method below 40 statements.
- Out of scope: do not change {{P}}REPL_SAMPLE. Do not create database tables.
# 7. Acceptance
- The function module and the class are active and have no syntax error.
- The hidden tests pass.
- Write ABAP Unit tests for the function module in a global test class.

View File

@@ -0,0 +1,99 @@
{
"id": "G0011",
"category": "A",
"object_type": "FUNC",
"difficulty": 2,
"release_target": "v758",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 40,
"max_activations": 10
},
"seed": [
{
"type": "FUGR",
"name": "{{P}}FG_REPL",
"description": "Replenishment function group"
},
{
"type": "FUNC",
"name": "{{P}}REPL_SAMPLE",
"functionGroup": "{{P}}FG_REPL",
"file": "seed/repl_sample.func.abap",
"description": "Sample bin information module"
}
],
"contract": [
{
"type": "CLAS",
"name": "{{P}}CL_REPL"
},
{
"type": "FUNC",
"name": "{{P}}REPL_CALC",
"functionGroup": "{{P}}FG_REPL",
"params": [
{
"name": "iv_stock",
"type": "decfloat34"
},
{
"name": "iv_reorder_point",
"type": "decfloat34"
},
{
"name": "iv_target_stock",
"type": "decfloat34"
},
{
"name": "iv_pack_size",
"type": "decfloat34"
},
{
"name": "iv_min_qty",
"type": "decfloat34"
},
{
"name": "ev_qty",
"type": "decfloat34"
}
]
}
],
"out_of_scope": [
"{{P}}REPL_SAMPLE"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T14_HIDDEN",
"file": "hidden/t14_hidden.clas.abap",
"description": "T14 hidden tests"
}
],
"reference": [
{
"type": "CLAS",
"name": "{{P}}CL_REPL",
"file": "reference/cl_repl.clas.abap",
"description": "Replenishment quantity calculator"
},
{
"type": "FUNC",
"name": "{{P}}REPL_CALC",
"functionGroup": "{{P}}FG_REPL",
"file": "reference/repl_calc.func.abap",
"description": "Replenishment function module"
},
{
"type": "CLAS",
"name": "{{P}}T14_TEST",
"file": "reference/t14_test.clas.abap",
"description": "T14 own tests"
}
],
"craft_checks": [
"method_length",
"clean_abap"
]
}

View File

@@ -0,0 +1,14 @@
{
"id": "G0012",
"pool": "eval",
"object_type": "FUNC",
"category": "F",
"attempts": [
{
"stage": "validate",
"oracle": 100.0,
"null": 0
}
],
"accepted": true
}

View File

@@ -0,0 +1,120 @@
CLASS {{p}}t14_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS call
IMPORTING iv_quantity TYPE decfloat34
iv_scrap TYPE decfloat34
iv_rework TYPE decfloat34 DEFAULT 0
EXPORTING ev_pct TYPE decfloat34
ev_severity TYPE string
ev_rework TYPE decfloat34
ev_subrc TYPE sysubrc.
METHODS percent_rounds_half_up FOR TESTING.
METHODS zero_scrap_is_ok FOR TESTING.
METHODS low_upper_boundary FOR TESTING.
METHODS high_lower_boundary FOR TESTING.
METHODS high_upper_boundary FOR TESTING.
METHODS critical_above_twenty FOR TESTING.
METHODS rework_unchanged_for_low FOR TESTING.
METHODS rework_set_for_high FOR TESTING.
METHODS quantity_zero_raises FOR TESTING.
METHODS scrap_negative_raises FOR TESTING.
METHODS scrap_above_quantity_raises FOR TESTING.
ENDCLASS.
CLASS {{p}}t14_hidden IMPLEMENTATION.
METHOD call.
ev_pct = 0.
ev_severity = ``.
ev_rework = iv_rework.
CALL FUNCTION '{{P}}PRD_SCRAP'
EXPORTING
iv_quantity = iv_quantity
iv_scrap = iv_scrap
IMPORTING
ev_scrap_pct = ev_pct
ev_severity = ev_severity
CHANGING
cv_rework_qty = ev_rework
EXCEPTIONS
invalid_quantity = 1
scrap_too_high = 2
OTHERS = 3.
ev_subrc = sy-subrc.
ENDMETHOD.
METHOD percent_rounds_half_up.
call( EXPORTING iv_quantity = 800 iv_scrap = 7
IMPORTING ev_pct = DATA(lv_pct) ev_severity = DATA(lv_sev) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '0.9' ) act = lv_pct ).
cl_abap_unit_assert=>assert_equals( exp = `LOW` act = lv_sev ).
ENDMETHOD.
METHOD zero_scrap_is_ok.
call( EXPORTING iv_quantity = 100 iv_scrap = 0
IMPORTING ev_pct = DATA(lv_pct) ev_severity = DATA(lv_sev) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 0 ) act = lv_pct ).
cl_abap_unit_assert=>assert_equals( exp = `OK` act = lv_sev ).
ENDMETHOD.
METHOD low_upper_boundary.
call( EXPORTING iv_quantity = 100 iv_scrap = 5
IMPORTING ev_pct = DATA(lv_pct) ev_severity = DATA(lv_sev) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 5 ) act = lv_pct ).
cl_abap_unit_assert=>assert_equals( exp = `LOW` act = lv_sev ).
ENDMETHOD.
METHOD high_lower_boundary.
call( EXPORTING iv_quantity = 100 iv_scrap = '5.1'
IMPORTING ev_pct = DATA(lv_pct) ev_severity = DATA(lv_sev) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '5.1' ) act = lv_pct ).
cl_abap_unit_assert=>assert_equals( exp = `HIGH` act = lv_sev ).
ENDMETHOD.
METHOD high_upper_boundary.
call( EXPORTING iv_quantity = 100 iv_scrap = 20
IMPORTING ev_pct = DATA(lv_pct) ev_severity = DATA(lv_sev) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 20 ) act = lv_pct ).
cl_abap_unit_assert=>assert_equals( exp = `HIGH` act = lv_sev ).
ENDMETHOD.
METHOD critical_above_twenty.
call( EXPORTING iv_quantity = 100 iv_scrap = '20.1'
IMPORTING ev_pct = DATA(lv_pct) ev_severity = DATA(lv_sev) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '20.1' ) act = lv_pct ).
cl_abap_unit_assert=>assert_equals( exp = `CRITICAL` act = lv_sev ).
ENDMETHOD.
METHOD rework_unchanged_for_low.
call( EXPORTING iv_quantity = 200 iv_scrap = 4 iv_rework = 99
IMPORTING ev_rework = DATA(lv_rework) ev_severity = DATA(lv_sev) ).
cl_abap_unit_assert=>assert_equals( exp = `LOW` act = lv_sev ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 99 ) act = lv_rework ).
ENDMETHOD.
METHOD rework_set_for_high.
call( EXPORTING iv_quantity = 100 iv_scrap = 10 iv_rework = 99
IMPORTING ev_rework = DATA(lv_rework) ev_severity = DATA(lv_sev) ).
cl_abap_unit_assert=>assert_equals( exp = `HIGH` act = lv_sev ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 10 ) act = lv_rework ).
ENDMETHOD.
METHOD quantity_zero_raises.
call( EXPORTING iv_quantity = 0 iv_scrap = 0
IMPORTING ev_subrc = DATA(lv_rc) ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lv_rc ).
ENDMETHOD.
METHOD scrap_negative_raises.
call( EXPORTING iv_quantity = 100 iv_scrap = -1
IMPORTING ev_subrc = DATA(lv_rc) ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lv_rc ).
ENDMETHOD.
METHOD scrap_above_quantity_raises.
call( EXPORTING iv_quantity = 100 iv_scrap = 101
IMPORTING ev_subrc = DATA(lv_rc) ).
cl_abap_unit_assert=>assert_equals( exp = 2 act = lv_rc ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,41 @@
FUNCTION {{p}}prd_scrap
IMPORTING
iv_quantity TYPE decfloat34
iv_scrap TYPE decfloat34
EXPORTING
ev_scrap_pct TYPE decfloat34
ev_severity TYPE string
CHANGING
cv_rework_qty TYPE decfloat34
EXCEPTIONS
invalid_quantity
scrap_too_high.
IF iv_quantity <= 0 OR iv_scrap < 0.
RAISE invalid_quantity.
ENDIF.
IF iv_scrap > iv_quantity.
RAISE scrap_too_high.
ENDIF.
ev_scrap_pct = round( val = iv_scrap / iv_quantity * 100
dec = 1
mode = cl_abap_math=>round_half_up ).
IF ev_scrap_pct = 0.
ev_severity = 'OK'.
ELSEIF ev_scrap_pct <= 5.
ev_severity = 'LOW'.
ELSEIF ev_scrap_pct <= 20.
ev_severity = 'HIGH'.
ELSE.
ev_severity = 'CRITICAL'.
ENDIF.
CASE ev_severity.
WHEN 'HIGH' OR 'CRITICAL'.
cv_rework_qty = iv_scrap.
ENDCASE.
ENDFUNCTION.

View File

@@ -0,0 +1,90 @@
CLASS {{p}}t14_test DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS result_for
IMPORTING iv_quantity TYPE decfloat34
iv_scrap TYPE decfloat34
iv_rework TYPE decfloat34 DEFAULT 0
EXPORTING ev_pct TYPE decfloat34
ev_severity TYPE string
ev_rework TYPE decfloat34.
METHODS percent_and_severity FOR TESTING.
METHODS severity_bands FOR TESTING.
METHODS rework_quantity FOR TESTING.
METHODS invalid_inputs FOR TESTING.
METHODS scrap_too_high FOR TESTING.
ENDCLASS.
CLASS {{p}}t14_test IMPLEMENTATION.
METHOD result_for.
ev_rework = iv_rework.
CALL FUNCTION '{{P}}PRD_SCRAP'
EXPORTING
iv_quantity = iv_quantity
iv_scrap = iv_scrap
IMPORTING
ev_scrap_pct = ev_pct
ev_severity = ev_severity
CHANGING
cv_rework_qty = ev_rework
EXCEPTIONS
invalid_quantity = 1
scrap_too_high = 2
OTHERS = 3.
cl_abap_unit_assert=>assert_subrc( exp = 0 ).
ENDMETHOD.
METHOD percent_and_severity.
result_for( EXPORTING iv_quantity = 800 iv_scrap = 7
IMPORTING ev_pct = DATA(lv_pct) ev_severity = DATA(lv_sev) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '0.9' ) act = lv_pct ).
cl_abap_unit_assert=>assert_equals( exp = `LOW` act = lv_sev ).
ENDMETHOD.
METHOD severity_bands.
result_for( EXPORTING iv_quantity = 100 iv_scrap = 0
IMPORTING ev_severity = DATA(lv_ok) ).
cl_abap_unit_assert=>assert_equals( exp = `OK` act = lv_ok ).
result_for( EXPORTING iv_quantity = 100 iv_scrap = 5
IMPORTING ev_severity = DATA(lv_low) ).
cl_abap_unit_assert=>assert_equals( exp = `LOW` act = lv_low ).
result_for( EXPORTING iv_quantity = 100 iv_scrap = 20
IMPORTING ev_severity = DATA(lv_high) ).
cl_abap_unit_assert=>assert_equals( exp = `HIGH` act = lv_high ).
result_for( EXPORTING iv_quantity = 100 iv_scrap = 21
IMPORTING ev_severity = DATA(lv_crit) ).
cl_abap_unit_assert=>assert_equals( exp = `CRITICAL` act = lv_crit ).
ENDMETHOD.
METHOD rework_quantity.
result_for( EXPORTING iv_quantity = 100 iv_scrap = 4 iv_rework = 99
IMPORTING ev_rework = DATA(lv_keep) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 99 ) act = lv_keep ).
result_for( EXPORTING iv_quantity = 100 iv_scrap = 10
IMPORTING ev_rework = DATA(lv_set) ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 10 ) act = lv_set ).
ENDMETHOD.
METHOD invalid_inputs.
DATA lv_pct TYPE decfloat34.
DATA lv_rework TYPE decfloat34.
CALL FUNCTION '{{P}}PRD_SCRAP'
EXPORTING iv_quantity = 0 iv_scrap = 0
IMPORTING ev_scrap_pct = lv_pct
CHANGING cv_rework_qty = lv_rework
EXCEPTIONS invalid_quantity = 1 OTHERS = 2.
cl_abap_unit_assert=>assert_equals( exp = 1 act = sy-subrc ).
ENDMETHOD.
METHOD scrap_too_high.
DATA lv_pct TYPE decfloat34.
DATA lv_rework TYPE decfloat34.
CALL FUNCTION '{{P}}PRD_SCRAP'
EXPORTING iv_quantity = 100 iv_scrap = 101
IMPORTING ev_scrap_pct = lv_pct
CHANGING cv_rework_qty = lv_rework
EXCEPTIONS scrap_too_high = 2 OTHERS = 1.
cl_abap_unit_assert=>assert_equals( exp = 2 act = sy-subrc ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,18 @@
FUNCTION {{p}}prd_sample
IMPORTING
iv_quantity TYPE decfloat34
iv_scrap TYPE decfloat34
EXPORTING
ev_scrap_pct TYPE decfloat34
ev_severity TYPE string
CHANGING
cv_rework_qty TYPE decfloat34
EXCEPTIONS
invalid_quantity
scrap_too_high.
" Sample confirmation module. All confirmation modules use this interface.
ev_scrap_pct = 0.
ev_severity = 'OK'.
ENDFUNCTION.

View File

@@ -0,0 +1,46 @@
# 1. Goal
Evaluate a production confirmation. Calculate the scrap percentage and the
severity of the scrap. The confirmation program calls one module for each
confirmation. The new module must have the same interface as the sample module,
because the confirmation program calls all modules in the same way.
# 2. Open questions
None.
# 3. Context
- Function group {{P}}FG_PROD exists in package $TMP.
- Function module {{P}}PRD_SAMPLE exists in this group. It is the sample
confirmation module. Its interface is the interface that all confirmation
modules must use.
# 4. Contract
- Create the function module {{P}}PRD_SCRAP in function group {{P}}FG_PROD.
- Use exactly the interface of {{P}}PRD_SAMPLE: the same parameter names, kinds,
types, and exceptions.
# 5. Business rules
1. If the confirmed quantity is less than or equal to 0, raise the exception
INVALID_QUANTITY.
2. If the scrap quantity is less than 0, raise the exception INVALID_QUANTITY.
3. If the scrap quantity is greater than the confirmed quantity, raise the
exception SCRAP_TOO_HIGH.
4. The scrap percentage is the scrap quantity divided by the confirmed quantity,
multiplied by 100. Round the result half up to 1 decimal. Write the result to
the scrap percentage parameter.
5. Set the severity from the rounded scrap percentage of rule 4:
- equal to 0: `OK`
- greater than 0 and less than or equal to 5: `LOW`
- greater than 5 and less than or equal to 20: `HIGH`
- greater than 20: `CRITICAL`
6. If the severity is `HIGH` or `CRITICAL`, set the rework quantity to the scrap
quantity. In all other cases, do not change the rework quantity.
# 6. Constraints
- Release target: 7.58.
- Coding standards: Clean ABAP. Keep every method below 40 statements.
- Out of scope: do not change {{P}}PRD_SAMPLE.
# 7. Acceptance
- The function module is active and has no syntax error.
- The hidden tests pass.
- Write ABAP Unit tests for the function module in a global test class.

View File

@@ -0,0 +1,84 @@
{
"id": "G0012",
"category": "F",
"object_type": "FUNC",
"difficulty": 2,
"release_target": "v758",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 40,
"max_activations": 10
},
"seed": [
{
"type": "FUGR",
"name": "{{P}}FG_PROD",
"description": "Production confirmation modules"
},
{
"type": "FUNC",
"name": "{{P}}PRD_SAMPLE",
"functionGroup": "{{P}}FG_PROD",
"file": "seed/prd_sample.func.abap",
"description": "Sample confirmation module"
}
],
"contract": [
{
"type": "FUNC",
"name": "{{P}}PRD_SCRAP",
"functionGroup": "{{P}}FG_PROD",
"params": [
{
"name": "iv_quantity",
"type": "decfloat34"
},
{
"name": "iv_scrap",
"type": "decfloat34"
},
{
"name": "ev_scrap_pct",
"type": "decfloat34"
},
{
"name": "ev_severity",
"type": "string"
},
{
"name": "cv_rework_qty",
"type": "decfloat34"
}
]
}
],
"out_of_scope": [
"{{P}}PRD_SAMPLE"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T14_HIDDEN",
"file": "hidden/t14_hidden.clas.abap",
"description": "T14 hidden tests"
}
],
"reference": [
{
"type": "FUNC",
"name": "{{P}}PRD_SCRAP",
"functionGroup": "{{P}}FG_PROD",
"file": "reference/prd_scrap.func.abap",
"description": "Scrap evaluation module"
},
{
"type": "CLAS",
"name": "{{P}}T14_TEST",
"file": "reference/t14_test.clas.abap",
"description": "T14 own tests"
}
],
"craft_checks": [
"method_length"
]
}

View File

@@ -0,0 +1,26 @@
{
"id": "G0013",
"pool": "eval",
"object_type": "FUNC",
"category": "I",
"attempts": [
{
"stage": "validate",
"oracle": 70.5,
"null": 0
},
{
"stage": "validate",
"oracle": 100.0,
"null": 0
},
{
"stage": "manual_fix",
"by": "Claude review 2026-10-03",
"change": "Removed own test class REPL_TEST from the contract and from the spec acceptance (own tests are the model's choice). REPL_TEST stays in the reference as own tests.",
"oracle": 100.0,
"null": 0
}
],
"accepted": true
}

View File

@@ -0,0 +1,108 @@
CLASS {{p}}t21_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
TYPES: BEGIN OF ty_result,
qty TYPE decfloat34,
action TYPE string,
subrc TYPE sysubrc,
END OF ty_result.
METHODS call
IMPORTING iv_stock TYPE decfloat34
iv_safety TYPE decfloat34
iv_lot_size TYPE decfloat34
iv_min_order TYPE decfloat34
RETURNING VALUE(rs_result) TYPE ty_result.
METHODS exact_multiple_lot FOR TESTING.
METHODS exact_multiple_single_lot FOR TESTING.
METHODS exact_multiple_with_minimum FOR TESTING.
METHODS partial_lot_rounds_up FOR TESTING.
METHODS stock_above_safety FOR TESTING.
METHODS stock_equals_safety FOR TESTING.
METHODS no_lot_size_no_rounding FOR TESTING.
METHODS minimum_order_applies FOR TESTING.
METHODS minimum_order_keeps_quantity FOR TESTING.
METHODS negative_lot_size FOR TESTING.
METHODS negative_min_order FOR TESTING.
ENDCLASS.
CLASS {{p}}t21_hidden IMPLEMENTATION.
METHOD call.
CALL FUNCTION '{{P}}REPL_QTY'
EXPORTING
iv_stock = iv_stock
iv_safety = iv_safety
iv_lot_size = iv_lot_size
iv_min_order = iv_min_order
IMPORTING
ev_order_qty = rs_result-qty
ev_action = rs_result-action
EXCEPTIONS
invalid_input = 1
OTHERS = 2.
rs_result-subrc = sy-subrc.
ENDMETHOD.
METHOD exact_multiple_lot.
DATA(ls_result) = call( iv_stock = 0 iv_safety = 100 iv_lot_size = 50 iv_min_order = 0 ).
cl_abap_unit_assert=>assert_equals( exp = 0 act = ls_result-subrc ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 100 ) act = ls_result-qty ).
cl_abap_unit_assert=>assert_equals( exp = `ORDER` act = ls_result-action ).
ENDMETHOD.
METHOD exact_multiple_single_lot.
DATA(ls_result) = call( iv_stock = 0 iv_safety = 50 iv_lot_size = 50 iv_min_order = 0 ).
cl_abap_unit_assert=>assert_equals( exp = 0 act = ls_result-subrc ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 50 ) act = ls_result-qty ).
ENDMETHOD.
METHOD exact_multiple_with_minimum.
DATA(ls_result) = call( iv_stock = 0 iv_safety = 100 iv_lot_size = 50 iv_min_order = 120 ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 120 ) act = ls_result-qty ).
cl_abap_unit_assert=>assert_equals( exp = `ORDER` act = ls_result-action ).
ENDMETHOD.
METHOD partial_lot_rounds_up.
DATA(ls_result) = call( iv_stock = 0 iv_safety = 110 iv_lot_size = 50 iv_min_order = 0 ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 150 ) act = ls_result-qty ).
cl_abap_unit_assert=>assert_equals( exp = `ORDER` act = ls_result-action ).
ENDMETHOD.
METHOD stock_above_safety.
DATA(ls_result) = call( iv_stock = 200 iv_safety = 100 iv_lot_size = 50 iv_min_order = 50 ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 0 ) act = ls_result-qty ).
cl_abap_unit_assert=>assert_equals( exp = `NONE` act = ls_result-action ).
ENDMETHOD.
METHOD stock_equals_safety.
DATA(ls_result) = call( iv_stock = 100 iv_safety = 100 iv_lot_size = 50 iv_min_order = 0 ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 0 ) act = ls_result-qty ).
cl_abap_unit_assert=>assert_equals( exp = `NONE` act = ls_result-action ).
ENDMETHOD.
METHOD no_lot_size_no_rounding.
DATA(ls_result) = call( iv_stock = 0 iv_safety = '37.5' iv_lot_size = 0 iv_min_order = 0 ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '37.5' ) act = ls_result-qty ).
cl_abap_unit_assert=>assert_equals( exp = `ORDER` act = ls_result-action ).
ENDMETHOD.
METHOD minimum_order_applies.
DATA(ls_result) = call( iv_stock = 0 iv_safety = 10 iv_lot_size = 0 iv_min_order = 25 ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 25 ) act = ls_result-qty ).
ENDMETHOD.
METHOD minimum_order_keeps_quantity.
DATA(ls_result) = call( iv_stock = 0 iv_safety = 100 iv_lot_size = 0 iv_min_order = 25 ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 100 ) act = ls_result-qty ).
ENDMETHOD.
METHOD negative_lot_size.
DATA(ls_result) = call( iv_stock = 0 iv_safety = 100 iv_lot_size = -1 iv_min_order = 0 ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = ls_result-subrc ).
ENDMETHOD.
METHOD negative_min_order.
DATA(ls_result) = call( iv_stock = 0 iv_safety = 100 iv_lot_size = 0 iv_min_order = -1 ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = ls_result-subrc ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,38 @@
FUNCTION {{p}}repl_qty
IMPORTING
iv_stock TYPE decfloat34
iv_safety TYPE decfloat34
iv_lot_size TYPE decfloat34
iv_min_order TYPE decfloat34
EXPORTING
ev_order_qty TYPE decfloat34
ev_action TYPE string
EXCEPTIONS
invalid_input.
IF iv_lot_size < 0 OR iv_min_order < 0.
RAISE invalid_input.
ENDIF.
DATA lv_required TYPE decfloat34.
lv_required = iv_safety - iv_stock.
IF lv_required <= 0.
ev_order_qty = 0.
ev_action = 'NONE'.
RETURN.
ENDIF.
IF iv_lot_size > 0.
ev_order_qty = ceil( lv_required / iv_lot_size ) * iv_lot_size.
ELSE.
ev_order_qty = lv_required.
ENDIF.
IF ev_order_qty < iv_min_order.
ev_order_qty = iv_min_order.
ENDIF.
ev_action = 'ORDER'.
ENDFUNCTION.

View File

@@ -0,0 +1,65 @@
CLASS {{p}}repl_test DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
TYPES: BEGIN OF ty_result,
qty TYPE decfloat34,
action TYPE string,
subrc TYPE sysubrc,
END OF ty_result.
METHODS call
IMPORTING iv_stock TYPE decfloat34
iv_safety TYPE decfloat34
iv_lot_size TYPE decfloat34
iv_min_order TYPE decfloat34
RETURNING VALUE(rs_result) TYPE ty_result.
METHODS exact_multiple FOR TESTING.
METHODS partial_lot FOR TESTING.
METHODS no_order FOR TESTING.
METHODS minimum_order FOR TESTING.
METHODS invalid_input FOR TESTING.
ENDCLASS.
CLASS {{p}}repl_test IMPLEMENTATION.
METHOD call.
CALL FUNCTION '{{P}}REPL_QTY'
EXPORTING
iv_stock = iv_stock
iv_safety = iv_safety
iv_lot_size = iv_lot_size
iv_min_order = iv_min_order
IMPORTING
ev_order_qty = rs_result-qty
ev_action = rs_result-action
EXCEPTIONS
invalid_input = 1
OTHERS = 2.
rs_result-subrc = sy-subrc.
ENDMETHOD.
METHOD exact_multiple.
DATA(ls_result) = call( iv_stock = 0 iv_safety = 100 iv_lot_size = 50 iv_min_order = 0 ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 100 ) act = ls_result-qty ).
ENDMETHOD.
METHOD partial_lot.
DATA(ls_result) = call( iv_stock = 0 iv_safety = 101 iv_lot_size = 50 iv_min_order = 0 ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 150 ) act = ls_result-qty ).
ENDMETHOD.
METHOD no_order.
DATA(ls_result) = call( iv_stock = 300 iv_safety = 100 iv_lot_size = 50 iv_min_order = 0 ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 0 ) act = ls_result-qty ).
cl_abap_unit_assert=>assert_equals( exp = `NONE` act = ls_result-action ).
ENDMETHOD.
METHOD minimum_order.
DATA(ls_result) = call( iv_stock = 0 iv_safety = 5 iv_lot_size = 0 iv_min_order = 20 ).
cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 20 ) act = ls_result-qty ).
ENDMETHOD.
METHOD invalid_input.
DATA(ls_result) = call( iv_stock = 0 iv_safety = 100 iv_lot_size = -5 iv_min_order = 0 ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = ls_result-subrc ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,38 @@
FUNCTION {{p}}repl_qty
IMPORTING
iv_stock TYPE decfloat34
iv_safety TYPE decfloat34
iv_lot_size TYPE decfloat34
iv_min_order TYPE decfloat34
EXPORTING
ev_order_qty TYPE decfloat34
ev_action TYPE string
EXCEPTIONS
invalid_input.
IF iv_lot_size < 0.
RAISE invalid_input.
ENDIF.
DATA lv_required TYPE decfloat34.
lv_required = iv_safety - iv_stock.
IF lv_required <= 0.
ev_order_qty = 0.
ev_action = 'NONE'.
RETURN.
ENDIF.
IF iv_lot_size > 0.
ev_order_qty = ( floor( lv_required / iv_lot_size ) + 1 ) * iv_lot_size.
ELSE.
ev_order_qty = lv_required.
ENDIF.
IF ev_order_qty < iv_min_order.
ev_order_qty = iv_min_order.
ENDIF.
ev_action = 'ORDER'.
ENDFUNCTION.

View File

@@ -0,0 +1,38 @@
FUNCTION {{p}}repl_qty_old
IMPORTING
iv_stock TYPE decfloat34
iv_safety TYPE decfloat34
iv_lot_size TYPE decfloat34
iv_min_order TYPE decfloat34
EXPORTING
ev_order_qty TYPE decfloat34
ev_action TYPE string
EXCEPTIONS
invalid_input.
IF iv_lot_size < 0.
RAISE invalid_input.
ENDIF.
DATA lv_required TYPE decfloat34.
lv_required = iv_safety - iv_stock.
IF lv_required <= 0.
ev_order_qty = 0.
ev_action = 'NONE'.
RETURN.
ENDIF.
IF iv_lot_size > 0.
ev_order_qty = ( floor( lv_required / iv_lot_size ) + 1 ) * iv_lot_size.
ELSE.
ev_order_qty = lv_required.
ENDIF.
IF ev_order_qty < iv_min_order.
ev_order_qty = iv_min_order.
ENDIF.
ev_action = 'ORDER'.
ENDFUNCTION.

View File

@@ -0,0 +1,48 @@
# 1. Goal
The function module {{P}}REPL_QTY_OLD calculates the replenishment order quantity for a material. Users report two defects:
- When the required quantity is exactly a multiple of the lot size, the module orders one lot too many.
- The module accepts a negative minimum order quantity. It must report an error instead.
The module {{P}}REPL_QTY_OLD is used by a released interface and must not change. Deliver the corrected calculation in the new function module {{P}}REPL_QTY.
# 2. Open questions
None.
# 3. Context
- Function group {{P}}FG_REPL exists in package $TMP.
- Function module {{P}}REPL_QTY_OLD exists in this group. It is the module in production. It has the two defects above.
- The replenishment program will call {{P}}REPL_QTY.
# 4. Contract
Create the function module {{P}}REPL_QTY in function group {{P}}FG_REPL with this interface:
IMPORTING
iv_stock TYPE decfloat34
iv_safety TYPE decfloat34
iv_lot_size TYPE decfloat34
iv_min_order TYPE decfloat34
EXPORTING
ev_order_qty TYPE decfloat34
ev_action TYPE string
EXCEPTIONS
invalid_input
ev_action contains the value 'ORDER' or the value 'NONE'.
# 5. Business rules
1. If iv_lot_size is less than 0, or if iv_min_order is less than 0, raise the exception INVALID_INPUT.
2. The required quantity is iv_safety minus iv_stock.
3. If the required quantity is less than or equal to 0, ev_order_qty is 0 and ev_action is 'NONE'. Rules 4 to 6 do not apply then.
4. If iv_lot_size is greater than 0, round the required quantity up to the next multiple of iv_lot_size. A required quantity that is already a multiple of iv_lot_size does not change.
5. If iv_lot_size is 0, do not round.
6. If ev_order_qty is less than iv_min_order, set ev_order_qty to iv_min_order.
7. If ev_order_qty is greater than 0, ev_action is 'ORDER'. Otherwise ev_action is 'NONE'.
# 6. Constraints
- Release target: 8.16.
- Coding standards: Clean ABAP. Keep methods below 40 statements.
- Out of scope: do not change the function group {{P}}FG_REPL. Do not change {{P}}REPL_QTY_OLD.
# 7. Acceptance
- The function module {{P}}REPL_QTY is active and has no syntax error.
- The hidden tests pass.
- Write ABAP Unit tests for the function module in a global test class.

View File

@@ -0,0 +1,89 @@
{
"id": "G0013",
"category": "I",
"object_type": "FUNC",
"difficulty": 2,
"release_target": "v816",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 40,
"max_activations": 10
},
"seed": [
{
"type": "FUGR",
"name": "{{P}}FG_REPL",
"description": "Replenishment function group"
},
{
"type": "FUNC",
"name": "{{P}}REPL_QTY_OLD",
"functionGroup": "{{P}}FG_REPL",
"file": "seed/repl_qty_old.func.abap",
"description": "Faulty replenishment order quantity module"
}
],
"contract": [
{
"type": "FUNC",
"name": "{{P}}REPL_QTY",
"functionGroup": "{{P}}FG_REPL",
"params": [
{
"name": "iv_stock",
"type": "decfloat34"
},
{
"name": "iv_safety",
"type": "decfloat34"
},
{
"name": "iv_lot_size",
"type": "decfloat34"
},
{
"name": "iv_min_order",
"type": "decfloat34"
},
{
"name": "ev_order_qty",
"type": "decfloat34"
},
{
"name": "ev_action",
"type": "string"
}
]
}
],
"out_of_scope": [
"{{P}}FG_REPL",
"{{P}}REPL_QTY_OLD"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T21_HIDDEN",
"file": "hidden/t21_hidden.clas.abap",
"description": "T21 hidden tests"
}
],
"reference": [
{
"type": "FUNC",
"name": "{{P}}REPL_QTY",
"functionGroup": "{{P}}FG_REPL",
"file": "reference/repl_qty.func.abap",
"description": "Corrected replenishment order quantity module"
},
{
"type": "CLAS",
"name": "{{P}}REPL_TEST",
"file": "reference/t21_test.clas.abap",
"description": "Own tests of the reference (not contract)"
}
],
"craft_checks": [
"method_length"
]
}

View File

@@ -0,0 +1,19 @@
{
"id": "G0014",
"pool": "eval",
"object_type": "PROG",
"category": "B",
"attempts": [
{
"stage": "validate",
"oracle": 93.3,
"null": 0
},
{
"stage": "validate",
"oracle": 100.0,
"null": 0
}
],
"accepted": true
}

View File

@@ -0,0 +1,112 @@
CLASS {{p}}t15_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
TYPES tt_plant TYPE RANGE OF {{p}}movmt-plant.
METHODS run
IMPORTING iv_from TYPE d
iv_to TYPE d
it_plant TYPE tt_plant OPTIONAL
RETURNING VALUE(rr_data) TYPE REF TO data.
METHODS value_of
IMPORTING ir_data TYPE REF TO data
iv_row TYPE i
iv_col TYPE string
RETURNING VALUE(rv_value) TYPE string.
METHODS lines_of
IMPORTING ir_data TYPE REF TO data
RETURNING VALUE(rv_lines) TYPE i.
METHODS all_materials_in_range FOR TESTING.
METHODS receipt_and_issue_split FOR TESTING.
METHODS sorted_by_balance FOR TESTING.
METHODS plant_filter FOR TESTING.
METHODS date_range_filter FOR TESTING.
METHODS ignored_move_types FOR TESTING.
ENDCLASS.
CLASS {{p}}t15_hidden IMPLEMENTATION.
METHOD run.
cl_salv_bs_runtime_info=>set( display = abap_false metadata = abap_false data = abap_true ).
SUBMIT {{p}}stock_bal
WITH p_from = iv_from
WITH p_to = iv_to
WITH s_plant IN it_plant
AND RETURN.
TRY.
cl_salv_bs_runtime_info=>get_data_ref( IMPORTING r_data = rr_data ).
CATCH cx_salv_bs_sc_runtime_info.
ENDTRY.
cl_salv_bs_runtime_info=>clear_all( ).
ENDMETHOD.
METHOD lines_of.
FIELD-SYMBOLS <lt_data> TYPE STANDARD TABLE.
IF ir_data IS BOUND.
ASSIGN ir_data->* TO <lt_data>.
rv_lines = lines( <lt_data> ).
ENDIF.
ENDMETHOD.
METHOD value_of.
FIELD-SYMBOLS <lt_data> TYPE STANDARD TABLE.
ASSIGN ir_data->* TO <lt_data>.
ASSIGN <lt_data>[ iv_row ] TO FIELD-SYMBOL(<ls_line>).
ASSIGN COMPONENT iv_col OF STRUCTURE <ls_line> TO FIELD-SYMBOL(<lv_value>).
cl_abap_unit_assert=>assert_subrc( exp = 0 msg = |Column { iv_col } missing| ).
rv_value = condense( CONV string( <lv_value> ) ).
" The ALV data can show a negative number with a trailing sign.
DATA(lv_length) = strlen( rv_value ).
IF lv_length > 1 AND substring( val = rv_value off = lv_length - 1 len = 1 ) = '-'.
rv_value = '-' && substring( val = rv_value off = 0 len = lv_length - 1 ).
ENDIF.
ENDMETHOD.
METHOD all_materials_in_range.
DATA(lr_data) = run( iv_from = '20260101' iv_to = '20260131' ).
cl_abap_unit_assert=>assert_equals( exp = 2 act = lines_of( lr_data ) ).
cl_abap_unit_assert=>assert_equals( exp = `MAT-001` act = value_of( ir_data = lr_data iv_row = 1 iv_col = `MATERIAL` ) ).
cl_abap_unit_assert=>assert_equals( exp = `120` act = value_of( ir_data = lr_data iv_row = 1 iv_col = `BALANCE` ) ).
cl_abap_unit_assert=>assert_equals( exp = `MAT-002` act = value_of( ir_data = lr_data iv_row = 2 iv_col = `MATERIAL` ) ).
cl_abap_unit_assert=>assert_equals( exp = `-20` act = value_of( ir_data = lr_data iv_row = 2 iv_col = `BALANCE` ) ).
ENDMETHOD.
METHOD receipt_and_issue_split.
DATA(lr_data) = run( iv_from = '20260101' iv_to = '20260131' ).
cl_abap_unit_assert=>assert_equals( exp = `150` act = value_of( ir_data = lr_data iv_row = 1 iv_col = `RECEIPT_QTY` ) ).
cl_abap_unit_assert=>assert_equals( exp = `30` act = value_of( ir_data = lr_data iv_row = 1 iv_col = `ISSUE_QTY` ) ).
cl_abap_unit_assert=>assert_equals( exp = `40` act = value_of( ir_data = lr_data iv_row = 2 iv_col = `RECEIPT_QTY` ) ).
cl_abap_unit_assert=>assert_equals( exp = `60` act = value_of( ir_data = lr_data iv_row = 2 iv_col = `ISSUE_QTY` ) ).
ENDMETHOD.
METHOD sorted_by_balance.
DATA(lr_data) = run( iv_from = '20251201' iv_to = '20260131' ).
cl_abap_unit_assert=>assert_equals( exp = 3 act = lines_of( lr_data ) ).
cl_abap_unit_assert=>assert_equals( exp = `MAT-001` act = value_of( ir_data = lr_data iv_row = 1 iv_col = `MATERIAL` ) ).
cl_abap_unit_assert=>assert_equals( exp = `MAT-004` act = value_of( ir_data = lr_data iv_row = 2 iv_col = `MATERIAL` ) ).
cl_abap_unit_assert=>assert_equals( exp = `MAT-002` act = value_of( ir_data = lr_data iv_row = 3 iv_col = `MATERIAL` ) ).
ENDMETHOD.
METHOD plant_filter.
DATA(lr_data) = run( iv_from = '20260101' iv_to = '20260131'
it_plant = VALUE #( ( sign = 'I' option = 'EQ' low = '1000' ) ) ).
cl_abap_unit_assert=>assert_equals( exp = 2 act = lines_of( lr_data ) ).
cl_abap_unit_assert=>assert_equals( exp = `MAT-001` act = value_of( ir_data = lr_data iv_row = 1 iv_col = `MATERIAL` ) ).
cl_abap_unit_assert=>assert_equals( exp = `70` act = value_of( ir_data = lr_data iv_row = 1 iv_col = `BALANCE` ) ).
ENDMETHOD.
METHOD date_range_filter.
DATA(lr_data) = run( iv_from = '20260101' iv_to = '20260109' ).
cl_abap_unit_assert=>assert_equals( exp = 2 act = lines_of( lr_data ) ).
cl_abap_unit_assert=>assert_equals( exp = `100` act = value_of( ir_data = lr_data iv_row = 1 iv_col = `BALANCE` ) ).
cl_abap_unit_assert=>assert_equals( exp = `40` act = value_of( ir_data = lr_data iv_row = 2 iv_col = `BALANCE` ) ).
ENDMETHOD.
METHOD ignored_move_types.
DATA(lr_data) = run( iv_from = '20260101' iv_to = '20260131'
it_plant = VALUE #( ( sign = 'I' option = 'EQ' low = '2000' ) ) ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lines_of( lr_data ) ).
cl_abap_unit_assert=>assert_equals( exp = `MAT-001` act = value_of( ir_data = lr_data iv_row = 1 iv_col = `MATERIAL` ) ).
cl_abap_unit_assert=>assert_equals( exp = `50` act = value_of( ir_data = lr_data iv_row = 1 iv_col = `RECEIPT_QTY` ) ).
cl_abap_unit_assert=>assert_equals( exp = `0` act = value_of( ir_data = lr_data iv_row = 1 iv_col = `ISSUE_QTY` ) ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,127 @@
REPORT {{p}}stock_bal.
DATA gv_plant TYPE {{p}}movmt-plant.
PARAMETERS p_from TYPE d OBLIGATORY.
PARAMETERS p_to TYPE d OBLIGATORY.
SELECT-OPTIONS s_plant FOR gv_plant.
CLASS lcl_report DEFINITION FINAL.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_line,
material TYPE {{p}}movmt-material,
receipt_qty TYPE i,
issue_qty TYPE i,
balance TYPE i,
END OF ty_line,
tt_line TYPE STANDARD TABLE OF ty_line WITH EMPTY KEY,
tt_plant TYPE RANGE OF {{p}}movmt-plant.
METHODS select_balances
IMPORTING iv_from TYPE d
iv_to TYPE d
it_plant TYPE tt_plant
RETURNING VALUE(rt_lines) TYPE tt_line.
METHODS display
CHANGING ct_lines TYPE tt_line.
ENDCLASS.
CLASS lcl_report IMPLEMENTATION.
METHOD select_balances.
SELECT material,
SUM( CASE WHEN move_type = '101' OR move_type = '201'
THEN quantity
ELSE 0 END ) AS receipt_qty,
SUM( CASE WHEN move_type = '261' OR move_type = '262'
THEN quantity
ELSE 0 END ) AS issue_qty
FROM {{p}}movmt
WHERE move_date >= @iv_from
AND move_date <= @iv_to
AND plant IN @it_plant
AND move_type IN ( '101', '201', '261', '262' )
GROUP BY material
INTO CORRESPONDING FIELDS OF TABLE @rt_lines.
LOOP AT rt_lines ASSIGNING FIELD-SYMBOL(<ls_line>).
<ls_line>-balance = <ls_line>-receipt_qty - <ls_line>-issue_qty.
ENDLOOP.
SORT rt_lines BY balance DESCENDING.
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->select_balances( iv_from = p_from
iv_to = p_to
it_plant = s_plant[] ).
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 balances_per_material FOR TESTING.
METHODS plant_filter FOR TESTING.
METHODS negative_balance FOR TESTING.
ENDCLASS.
CLASS ltc_report IMPLEMENTATION.
METHOD class_setup.
go_env = cl_osql_test_environment=>create( i_dependency_list = VALUE #( ( '{{P}}MOVMT' ) ) ).
DATA lt_movmt TYPE STANDARD TABLE OF {{p}}movmt WITH EMPTY KEY.
lt_movmt = VALUE #(
( move_id = 1 material = 'MAT-A' plant = '1000' move_date = '20260105' move_type = '101' quantity = 100 )
( move_id = 2 material = 'MAT-A' plant = '1000' move_date = '20260110' move_type = '261' quantity = 30 )
( move_id = 3 material = 'MAT-B' plant = '2000' move_date = '20260112' move_type = '201' quantity = 40 )
( move_id = 4 material = 'MAT-B' plant = '2000' move_date = '20260113' move_type = '262' quantity = 90 )
( move_id = 5 material = 'MAT-C' plant = '2000' move_date = '20260112' move_type = '999' quantity = 500 ) ).
go_env->insert_test_data( lt_movmt ).
ENDMETHOD.
METHOD class_teardown.
go_env->destroy( ).
ENDMETHOD.
METHOD balances_per_material.
DATA(lt_lines) = NEW lcl_report( )->select_balances(
iv_from = '20260101' iv_to = '20260131' it_plant = VALUE #( ) ).
cl_abap_unit_assert=>assert_equals( exp = 2 act = lines( lt_lines ) ).
cl_abap_unit_assert=>assert_equals( exp = 'MAT-A' act = lt_lines[ 1 ]-material ).
cl_abap_unit_assert=>assert_equals( exp = 70 act = lt_lines[ 1 ]-balance ).
cl_abap_unit_assert=>assert_equals( exp = 'MAT-B' act = lt_lines[ 2 ]-material ).
cl_abap_unit_assert=>assert_equals( exp = -50 act = lt_lines[ 2 ]-balance ).
ENDMETHOD.
METHOD plant_filter.
DATA(lt_lines) = NEW lcl_report( )->select_balances(
iv_from = '20260101'
iv_to = '20260131'
it_plant = VALUE #( ( sign = 'I' option = 'EQ' low = '1000' ) ) ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lines( lt_lines ) ).
cl_abap_unit_assert=>assert_equals( exp = 'MAT-A' act = lt_lines[ 1 ]-material ).
cl_abap_unit_assert=>assert_equals( exp = 30 act = lt_lines[ 1 ]-issue_qty ).
ENDMETHOD.
METHOD negative_balance.
DATA(lt_lines) = NEW lcl_report( )->select_balances(
iv_from = '20260101'
iv_to = '20260131'
it_plant = VALUE #( ( sign = 'I' option = 'EQ' low = '2000' ) ) ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lines( lt_lines ) ).
cl_abap_unit_assert=>assert_equals( exp = 'MAT-B' act = lt_lines[ 1 ]-material ).
cl_abap_unit_assert=>assert_equals( exp = -50 act = lt_lines[ 1 ]-balance ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,14 @@
@EndUserText.label : 'Stock movements'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table {{p}}movmt {
key client : abap.clnt not null;
key move_id : abap.int4 not null;
material : abap.char(10);
plant : abap.char(4);
move_date : abap.dats;
move_type : abap.char(3);
quantity : abap.int4;
}

View File

@@ -0,0 +1,19 @@
CLASS {{p}}t15_seed DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS {{p}}t15_seed IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DELETE FROM {{p}}movmt.
INSERT {{p}}movmt FROM TABLE @( VALUE #(
( move_id = 1 material = 'MAT-001' plant = '1000' move_date = '20260105' move_type = '101' quantity = 100 )
( move_id = 2 material = 'MAT-001' plant = '1000' move_date = '20260110' move_type = '261' quantity = 30 )
( move_id = 3 material = 'MAT-001' plant = '2000' move_date = '20260120' move_type = '101' quantity = 50 )
( move_id = 4 material = 'MAT-002' plant = '1000' move_date = '20260108' move_type = '201' quantity = 40 )
( move_id = 5 material = 'MAT-002' plant = '1000' move_date = '20260115' move_type = '262' quantity = 60 )
( move_id = 6 material = 'MAT-003' plant = '2000' move_date = '20260112' move_type = '999' quantity = 500 )
( move_id = 7 material = 'MAT-004' plant = '1000' move_date = '20251220' move_type = '101' quantity = 70 ) ) ).
out->write( sy-dbcnt ).
ENDMETHOD.
ENDCLASS.

View File

@@ -0,0 +1,43 @@
# 1. Goal
Show the stock balance for each material in a warehouse. The warehouse team uses
the list to compare the receipts and the issues of a period.
# 2. Open questions
None.
# 3. Context
- Table {{P}}MOVMT exists in package $TMP. It contains data.
Fields: MOVE_ID (INT4, key), MATERIAL (CHAR 10), PLANT (CHAR 4),
MOVE_DATE (DATS), MOVE_TYPE (CHAR 3), QUANTITY (INT4).
# 4. Contract
- Create the executable program {{P}}STOCK_BAL in package $TMP.
- Selection screen:
- Parameter P_FROM, type DATS, mandatory: the first movement date to include.
- Parameter P_TO, type DATS, mandatory: the last movement date to include.
- Select-option S_PLANT for the plant.
- Show the result with CL_SALV_TABLE. The output table has these columns, with
these names: MATERIAL, RECEIPT_QTY, ISSUE_QTY, BALANCE.
# 5. Business rules
1. A movement is in scope if MOVE_DATE is on or after P_FROM and on or before
P_TO, and PLANT is in S_PLANT.
2. An in-scope movement is a receipt if MOVE_TYPE is '101' or '201'. It is an
issue if MOVE_TYPE is '261' or '262'. All other in-scope movements are
ignored.
3. RECEIPT_QTY is the sum of QUANTITY of all receipts of the material.
ISSUE_QTY is the sum of QUANTITY of all issues of the material.
BALANCE is RECEIPT_QTY minus ISSUE_QTY.
4. Show one line for each material that has at least one receipt or issue. A
material with only ignored movements does not appear.
5. Sort the lines by BALANCE, the highest balance first.
# 6. Constraints
- Release target: 7.58.
- Coding standards: Clean ABAP. Read the movement data with one database access.
- Out of scope: do not change {{P}}MOVMT.
# 7. Acceptance
- The program is active and has no syntax error.
- The hidden tests pass.
- Write ABAP Unit tests as local test classes in the program.

View File

@@ -0,0 +1,61 @@
{
"id": "G0014",
"category": "B",
"object_type": "PROG",
"difficulty": 2,
"release_target": "v758",
"expected_outcome": "implement",
"budget": {
"max_tool_calls": 40,
"max_activations": 10
},
"seed": [
{
"type": "TABL",
"name": "{{P}}MOVMT",
"file": "seed/movmt.tabl.asabap",
"description": "Stock movements"
},
{
"type": "CLAS",
"name": "{{P}}T15_SEED",
"file": "seed/t15_seed.clas.abap",
"description": "T15 seed data",
"run": true
}
],
"contract": [
{
"type": "PROG",
"name": "{{P}}STOCK_BAL",
"parameters": [
"p_from",
"p_to",
"s_plant"
]
}
],
"out_of_scope": [
"{{P}}MOVMT"
],
"hidden_tests": [
{
"type": "CLAS",
"name": "{{P}}T15_HIDDEN",
"file": "hidden/t15_hidden.clas.abap",
"description": "T15 hidden tests"
}
],
"reference": [
{
"type": "PROG",
"name": "{{P}}STOCK_BAL",
"file": "reference/stock_bal.prog.abap",
"description": "Stock balance per material"
}
],
"craft_checks": [
"db_operation_in_loop",
"method_length"
]
}

View File

@@ -0,0 +1,19 @@
{
"id": "G0015",
"pool": "eval",
"object_type": "PROG",
"category": "B",
"attempts": [
{
"stage": "validate",
"oracle": 0,
"null": 0
},
{
"stage": "validate",
"oracle": 100.0,
"null": 0
}
],
"accepted": true
}

View File

@@ -0,0 +1,120 @@
CLASS {{p}}bill_age_test DEFINITION PUBLIC FINAL CREATE PUBLIC
FOR TESTING DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
TYPES tt_customer TYPE RANGE OF {{p}}bill-customer.
METHODS run
IMPORTING iv_date TYPE d
it_customer TYPE tt_customer OPTIONAL
RETURNING VALUE(rr_data) TYPE REF TO data.
METHODS value_of
IMPORTING ir_data TYPE REF TO data
iv_row TYPE i
iv_col TYPE string
RETURNING VALUE(rv_value) TYPE string.
METHODS lines_of
IMPORTING ir_data TYPE REF TO data
RETURNING VALUE(rv_lines) TYPE i.
METHODS only_open_invoices FOR TESTING.
METHODS paid_invoices_excluded FOR TESTING.
METHODS sorted_by_open_amount FOR TESTING.
METHODS count_and_amount FOR TESTING.
METHODS overdue_amount FOR TESTING.
METHODS no_overdue_amount FOR TESTING.
METHODS customer_filter FOR TESTING.
METHODS customer_filter_multiple FOR TESTING.
ENDCLASS.
CLASS {{p}}bill_age_test IMPLEMENTATION.
METHOD run.
cl_salv_bs_runtime_info=>set( display = abap_false metadata = abap_false data = abap_true ).
SUBMIT {{p}}bill_age WITH p_date = iv_date WITH s_cust IN it_customer AND RETURN.
TRY.
cl_salv_bs_runtime_info=>get_data_ref( IMPORTING r_data = rr_data ).
CATCH cx_salv_bs_sc_runtime_info.
ENDTRY.
cl_salv_bs_runtime_info=>clear_all( ).
ENDMETHOD.
METHOD lines_of.
FIELD-SYMBOLS <lt_data> TYPE STANDARD TABLE.
IF ir_data IS BOUND.
ASSIGN ir_data->* TO <lt_data>.
rv_lines = lines( <lt_data> ).
ENDIF.
ENDMETHOD.
METHOD value_of.
FIELD-SYMBOLS <lt_data> TYPE STANDARD TABLE.
ASSIGN ir_data->* TO <lt_data>.
ASSIGN <lt_data>[ iv_row ] TO FIELD-SYMBOL(<ls_line>).
ASSIGN COMPONENT iv_col OF STRUCTURE <ls_line> TO FIELD-SYMBOL(<lv_value>).
cl_abap_unit_assert=>assert_subrc( exp = 0 msg = |Column { iv_col } is missing| ).
rv_value = condense( CONV string( <lv_value> ) ).
ENDMETHOD.
METHOD only_open_invoices.
cl_abap_unit_assert=>assert_equals( exp = 4 act = lines_of( run( '20260101' ) ) ).
ENDMETHOD.
METHOD paid_invoices_excluded.
DATA(lr_data) = run( '20260101' ).
cl_abap_unit_assert=>assert_equals( exp = `C002`
act = value_of( ir_data = lr_data iv_row = 2 iv_col = `CUSTOMER` ) ).
cl_abap_unit_assert=>assert_equals( exp = `300.00`
act = value_of( ir_data = lr_data iv_row = 2 iv_col = `OPEN_AMOUNT` ) ).
ENDMETHOD.
METHOD sorted_by_open_amount.
DATA(lr_data) = run( '20260101' ).
cl_abap_unit_assert=>assert_equals( exp = `C004`
act = value_of( ir_data = lr_data iv_row = 1 iv_col = `CUSTOMER` ) ).
cl_abap_unit_assert=>assert_equals( exp = `C003`
act = value_of( ir_data = lr_data iv_row = 4 iv_col = `CUSTOMER` ) ).
ENDMETHOD.
METHOD count_and_amount.
DATA(lr_data) = run( '20260101' ).
cl_abap_unit_assert=>assert_equals( exp = `C001`
act = value_of( ir_data = lr_data iv_row = 3 iv_col = `CUSTOMER` ) ).
cl_abap_unit_assert=>assert_equals( exp = `2`
act = value_of( ir_data = lr_data iv_row = 3 iv_col = `OPEN_COUNT` ) ).
cl_abap_unit_assert=>assert_equals( exp = `150.00`
act = value_of( ir_data = lr_data iv_row = 3 iv_col = `OPEN_AMOUNT` ) ).
ENDMETHOD.
METHOD overdue_amount.
DATA(lr_data) = run( '20260301' ).
cl_abap_unit_assert=>assert_equals( exp = `100.00`
act = value_of( ir_data = lr_data iv_row = 3 iv_col = `OVERDUE_AMOUNT` ) ).
cl_abap_unit_assert=>assert_equals( exp = `300.00`
act = value_of( ir_data = lr_data iv_row = 2 iv_col = `OVERDUE_AMOUNT` ) ).
ENDMETHOD.
METHOD no_overdue_amount.
DATA(lr_data) = run( '20260101' ).
cl_abap_unit_assert=>assert_equals( exp = `0.00`
act = value_of( ir_data = lr_data iv_row = 1 iv_col = `OVERDUE_AMOUNT` ) ).
cl_abap_unit_assert=>assert_equals( exp = `0.00`
act = value_of( ir_data = lr_data iv_row = 3 iv_col = `OVERDUE_AMOUNT` ) ).
ENDMETHOD.
METHOD customer_filter.
DATA(lr_data) = run( iv_date = '20260101'
it_customer = VALUE #( ( sign = 'I' option = 'EQ' low = 'C001' ) ) ).
cl_abap_unit_assert=>assert_equals( exp = 1 act = lines_of( lr_data ) ).
cl_abap_unit_assert=>assert_equals( exp = `150.00`
act = value_of( ir_data = lr_data iv_row = 1 iv_col = `OPEN_AMOUNT` ) ).
ENDMETHOD.
METHOD customer_filter_multiple.
DATA(lr_data) = run( iv_date = '20260101'
it_customer = VALUE #( ( sign = 'I' option = 'EQ' low = 'C001' )
( sign = 'I' option = 'EQ' low = 'C002' ) ) ).
cl_abap_unit_assert=>assert_equals( exp = 2 act = lines_of( lr_data ) ).
cl_abap_unit_assert=>assert_equals( exp = `C002`
act = value_of( ir_data = lr_data iv_row = 1 iv_col = `CUSTOMER` ) ).
cl_abap_unit_assert=>assert_equals( exp = `C001`
act = value_of( ir_data = lr_data iv_row = 2 iv_col = `CUSTOMER` ) ).
ENDMETHOD.
ENDCLASS.

Some files were not shown because too many files have changed in this diff Show More