From 5de5e2851caeaaa8546f777098c1324b0047cd8f Mon Sep 17 00:00:00 2001 From: Kral Date: Sat, 3 Oct 2026 05:04:23 +0200 Subject: [PATCH] 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 --- CLAUDE.md | 37 +- docs/faz1-tasarim.md | 603 ++++++++++++++++++ docs/yol-haritasi.md | 161 +++++ harness/generator.py | 146 ++++- harness/pilot.py | 18 +- harness/runner.py | 28 +- tasks_gen/eval/G0003/generation.json | 29 + .../eval/G0003/hidden/t02_hidden.clas.abap | 119 ++++ .../G0003/reference/course_booking.clas.abap | 94 +++ .../reference/course_booking.testclasses.abap | 63 ++ tasks_gen/eval/G0003/seed/course.ddls.abap | 8 + tasks_gen/eval/G0003/seed/course.ddls.asddls | 9 + tasks_gen/eval/G0003/seed/course.tabl.asddls | 13 + .../eval/G0003/seed/seed_courses.clas.abap | 24 + tasks_gen/eval/G0003/spec.md | 68 ++ tasks_gen/eval/G0003/task.json | 58 ++ tasks_gen/eval/G0004/generation.json | 14 + .../G0004/hidden/stock_age_hidden.clas.abap | 207 ++++++ .../eval/G0004/reference/stock_age.clas.abap | 92 +++ .../reference/stock_age.testclasses.abap | 80 +++ .../eval/G0004/seed/if_stock_age.intf.abap | 45 ++ tasks_gen/eval/G0004/spec.md | 80 +++ tasks_gen/eval/G0004/task.json | 53 ++ tasks_gen/eval/G0005/generation.json | 29 + .../eval/G0005/hidden/t02_hidden.clas.abap | 158 +++++ .../reference/cx_freight_error.clas.abap | 11 + .../reference/cx_invalid_weight.clas.abap | 13 + .../G0005/reference/cx_unknown_zone.clas.abap | 11 + .../G0005/reference/cx_weight_limit.clas.abap | 14 + .../G0005/reference/freight_calc.clas.abap | 73 +++ .../reference/freight_calc.testclasses.abap | 98 +++ tasks_gen/eval/G0005/seed/seed_data.clas.abap | 18 + .../eval/G0005/seed/zone_master.clas.abap | 27 + tasks_gen/eval/G0005/seed/zones.tabl.abap | 10 + tasks_gen/eval/G0005/spec.md | 103 +++ tasks_gen/eval/G0005/task.json | 90 +++ tasks_gen/eval/G0006/generation.json | 30 + .../eval/G0006/hidden/t02_hidden.clas.abap | 154 +++++ .../reference/freight_calculator.clas.abap | 130 ++++ .../freight_calculator.testclasses.abap | 56 ++ .../G0006/seed/freight_calculator.clas.abap | 106 +++ .../seed/freight_calculator.testclasses.abap | 25 + tasks_gen/eval/G0006/spec.md | 67 ++ tasks_gen/eval/G0006/task.json | 54 ++ tasks_gen/eval/G0007/generation.json | 14 + .../eval/G0007/hidden/t02_hidden.clas.abap | 103 +++ .../reference/loyalty_evaluator.clas.abap | 79 +++ .../loyalty_evaluator.testclasses.abap | 77 +++ .../G0007/seed/if_loyalty_rules.intf.abap | 24 + tasks_gen/eval/G0007/spec.md | 57 ++ tasks_gen/eval/G0007/task.json | 51 ++ tasks_gen/eval/G0008/generation.json | 19 + .../eval/G0008/hidden/t07_hidden.clas.abap | 197 ++++++ .../G0008/reference/freight_calc.clas.abap | 149 +++++ .../reference/freight_calc.testclasses.abap | 114 ++++ .../eval/G0008/seed/if_freight_calc.intf.abap | 34 + tasks_gen/eval/G0008/spec.md | 61 ++ tasks_gen/eval/G0008/task.json | 51 ++ tasks_gen/eval/G0009/generation.json | 19 + .../eval/G0009/hidden/t02_hidden.clas.abap | 111 ++++ .../reference/shipping_cost_calc.clas.abap | 56 ++ .../shipping_cost_calc.testclasses.abap | 68 ++ .../G0009/seed/if_shipping_cost.intf.abap | 17 + .../G0009/seed/shipping_cost_calc.clas.abap | 53 ++ .../G0009/seed/shipping_cost_legacy.clas.abap | 53 ++ tasks_gen/eval/G0009/spec.md | 63 ++ tasks_gen/eval/G0009/task.json | 59 ++ tasks_gen/eval/G0010/generation.json | 25 + .../eval/G0010/hidden/t02_hidden.clas.abap | 173 +++++ .../reference/stock_cover_checker.clas.abap | 128 ++++ .../stock_cover_checker.testclasses.abap | 93 +++ .../eval/G0010/seed/if_stock_cover.intf.abap | 46 ++ tasks_gen/eval/G0010/spec.md | 70 ++ tasks_gen/eval/G0010/task.json | 52 ++ tasks_gen/eval/G0011/generation.json | 29 + .../eval/G0011/hidden/t14_hidden.clas.abap | 117 ++++ .../eval/G0011/reference/cl_repl.clas.abap | 38 ++ .../eval/G0011/reference/repl_calc.func.abap | 24 + .../eval/G0011/reference/t14_test.clas.abap | 100 +++ .../eval/G0011/seed/repl_sample.func.abap | 9 + tasks_gen/eval/G0011/spec.md | 66 ++ tasks_gen/eval/G0011/task.json | 99 +++ tasks_gen/eval/G0012/generation.json | 14 + .../eval/G0012/hidden/t14_hidden.clas.abap | 120 ++++ .../eval/G0012/reference/prd_scrap.func.abap | 41 ++ .../eval/G0012/reference/t14_test.clas.abap | 90 +++ .../eval/G0012/seed/prd_sample.func.abap | 18 + tasks_gen/eval/G0012/spec.md | 46 ++ tasks_gen/eval/G0012/task.json | 84 +++ tasks_gen/eval/G0013/generation.json | 26 + .../eval/G0013/hidden/t21_hidden.clas.abap | 108 ++++ .../eval/G0013/reference/repl_qty.func.abap | 38 ++ .../eval/G0013/reference/t21_test.clas.abap | 65 ++ tasks_gen/eval/G0013/seed/repl_qty.func.abap | 38 ++ .../eval/G0013/seed/repl_qty_old.func.abap | 38 ++ tasks_gen/eval/G0013/spec.md | 48 ++ tasks_gen/eval/G0013/task.json | 89 +++ tasks_gen/eval/G0014/generation.json | 19 + .../eval/G0014/hidden/t15_hidden.clas.abap | 112 ++++ .../eval/G0014/reference/stock_bal.prog.abap | 127 ++++ tasks_gen/eval/G0014/seed/movmt.tabl.asabap | 14 + tasks_gen/eval/G0014/seed/t15_seed.clas.abap | 19 + tasks_gen/eval/G0014/spec.md | 43 ++ tasks_gen/eval/G0014/task.json | 61 ++ tasks_gen/eval/G0015/generation.json | 19 + .../eval/G0015/hidden/bill_age_test.clas.abap | 120 ++++ .../eval/G0015/reference/bill_age.prog.abap | 95 +++ tasks_gen/eval/G0015/seed/bill.tabl.asabap | 13 + tasks_gen/eval/G0015/seed/bill_seed.clas.abap | 19 + tasks_gen/eval/G0015/spec.md | 42 ++ tasks_gen/eval/G0015/task.json | 60 ++ tasks_gen/eval/G0016/generation.json | 24 + .../eval/G0016/hidden/t15_hidden.clas.abap | 186 ++++++ .../eval/G0016/reference/stock_net.prog.abap | 132 ++++ tasks_gen/eval/G0016/seed/move.tabl.asabap | 14 + tasks_gen/eval/G0016/seed/seed_move.clas.abap | 20 + tasks_gen/eval/G0016/seed/stock_net.prog.abap | 82 +++ .../eval/G0016/seed/stock_net_old.prog.abap | 82 +++ tasks_gen/eval/G0016/spec.md | 56 ++ tasks_gen/eval/G0016/task.json | 70 ++ tasks_gen/eval/G0017/generation.json | 29 + .../eval/G0017/hidden/t21_hidden.clas.abap | 86 +++ .../G0017/reference/i_proj_effort.ddls.asddls | 22 + .../eval/G0017/reference/t21_test.clas.abap | 53 ++ tasks_gen/eval/G0017/seed/asgn.tabl.asabap | 13 + tasks_gen/eval/G0017/seed/proj.tabl.asabap | 11 + tasks_gen/eval/G0017/spec.md | 42 ++ tasks_gen/eval/G0017/task.json | 67 ++ tasks_gen/eval/G0018/generation.json | 24 + .../eval/G0018/hidden/t20_hidden.clas.abap | 79 +++ .../G0018/reference/i_customer.ddls.asddls | 9 + .../eval/G0018/reference/i_order.ddls.asddls | 15 + .../eval/G0018/reference/t20_test.clas.abap | 48 ++ tasks_gen/eval/G0018/seed/cust.tabl.asabap | 11 + tasks_gen/eval/G0018/seed/ord.tabl.asabap | 12 + tasks_gen/eval/G0018/seed/seed_data.clas.abap | 22 + tasks_gen/eval/G0018/spec.md | 46 ++ tasks_gen/eval/G0018/task.json | 73 +++ tasks_gen/eval/G0019/generation.json | 19 + .../eval/G0019/hidden/t20_hidden.clas.abap | 109 ++++ .../G0019/reference/i_sales_class.ddls.asddls | 14 + .../eval/G0019/reference/t20_test.clas.abap | 50 ++ tasks_gen/eval/G0019/seed/sale.tabl.asabap | 13 + tasks_gen/eval/G0019/spec.md | 44 ++ tasks_gen/eval/G0019/task.json | 60 ++ tasks_gen/eval/G0020/generation.json | 36 ++ .../eval/G0020/hidden/t20_hidden.clas.abap | 157 +++++ .../reference/i_book_revenue.ddls.asddls | 20 + .../eval/G0020/reference/t20_test.clas.abap | 62 ++ tasks_gen/eval/G0020/seed/book.tabl.asabap | 13 + tasks_gen/eval/G0020/seed/sale.tabl.asabap | 11 + tasks_gen/eval/G0020/spec.md | 43 ++ tasks_gen/eval/G0020/task.json | 76 +++ tasks_gen/eval/G0021/generation.json | 29 + .../eval/G0021/hidden/t16_hidden.clas.abap | 93 +++ .../G0021/reference/i_shipment.ddls.asddls | 23 + .../eval/G0021/reference/t16_test.clas.abap | 46 ++ tasks_gen/eval/G0021/seed/rail.tabl.asabap | 12 + tasks_gen/eval/G0021/seed/road.tabl.asabap | 12 + tasks_gen/eval/G0021/seed/seed_ship.clas.abap | 31 + tasks_gen/eval/G0021/spec.md | 42 ++ tasks_gen/eval/G0021/task.json | 73 +++ 162 files changed, 9943 insertions(+), 23 deletions(-) create mode 100644 docs/faz1-tasarim.md create mode 100644 docs/yol-haritasi.md create mode 100644 tasks_gen/eval/G0003/generation.json create mode 100644 tasks_gen/eval/G0003/hidden/t02_hidden.clas.abap create mode 100644 tasks_gen/eval/G0003/reference/course_booking.clas.abap create mode 100644 tasks_gen/eval/G0003/reference/course_booking.testclasses.abap create mode 100644 tasks_gen/eval/G0003/seed/course.ddls.abap create mode 100644 tasks_gen/eval/G0003/seed/course.ddls.asddls create mode 100644 tasks_gen/eval/G0003/seed/course.tabl.asddls create mode 100644 tasks_gen/eval/G0003/seed/seed_courses.clas.abap create mode 100644 tasks_gen/eval/G0003/spec.md create mode 100644 tasks_gen/eval/G0003/task.json create mode 100644 tasks_gen/eval/G0004/generation.json create mode 100644 tasks_gen/eval/G0004/hidden/stock_age_hidden.clas.abap create mode 100644 tasks_gen/eval/G0004/reference/stock_age.clas.abap create mode 100644 tasks_gen/eval/G0004/reference/stock_age.testclasses.abap create mode 100644 tasks_gen/eval/G0004/seed/if_stock_age.intf.abap create mode 100644 tasks_gen/eval/G0004/spec.md create mode 100644 tasks_gen/eval/G0004/task.json create mode 100644 tasks_gen/eval/G0005/generation.json create mode 100644 tasks_gen/eval/G0005/hidden/t02_hidden.clas.abap create mode 100644 tasks_gen/eval/G0005/reference/cx_freight_error.clas.abap create mode 100644 tasks_gen/eval/G0005/reference/cx_invalid_weight.clas.abap create mode 100644 tasks_gen/eval/G0005/reference/cx_unknown_zone.clas.abap create mode 100644 tasks_gen/eval/G0005/reference/cx_weight_limit.clas.abap create mode 100644 tasks_gen/eval/G0005/reference/freight_calc.clas.abap create mode 100644 tasks_gen/eval/G0005/reference/freight_calc.testclasses.abap create mode 100644 tasks_gen/eval/G0005/seed/seed_data.clas.abap create mode 100644 tasks_gen/eval/G0005/seed/zone_master.clas.abap create mode 100644 tasks_gen/eval/G0005/seed/zones.tabl.abap create mode 100644 tasks_gen/eval/G0005/spec.md create mode 100644 tasks_gen/eval/G0005/task.json create mode 100644 tasks_gen/eval/G0006/generation.json create mode 100644 tasks_gen/eval/G0006/hidden/t02_hidden.clas.abap create mode 100644 tasks_gen/eval/G0006/reference/freight_calculator.clas.abap create mode 100644 tasks_gen/eval/G0006/reference/freight_calculator.testclasses.abap create mode 100644 tasks_gen/eval/G0006/seed/freight_calculator.clas.abap create mode 100644 tasks_gen/eval/G0006/seed/freight_calculator.testclasses.abap create mode 100644 tasks_gen/eval/G0006/spec.md create mode 100644 tasks_gen/eval/G0006/task.json create mode 100644 tasks_gen/eval/G0007/generation.json create mode 100644 tasks_gen/eval/G0007/hidden/t02_hidden.clas.abap create mode 100644 tasks_gen/eval/G0007/reference/loyalty_evaluator.clas.abap create mode 100644 tasks_gen/eval/G0007/reference/loyalty_evaluator.testclasses.abap create mode 100644 tasks_gen/eval/G0007/seed/if_loyalty_rules.intf.abap create mode 100644 tasks_gen/eval/G0007/spec.md create mode 100644 tasks_gen/eval/G0007/task.json create mode 100644 tasks_gen/eval/G0008/generation.json create mode 100644 tasks_gen/eval/G0008/hidden/t07_hidden.clas.abap create mode 100644 tasks_gen/eval/G0008/reference/freight_calc.clas.abap create mode 100644 tasks_gen/eval/G0008/reference/freight_calc.testclasses.abap create mode 100644 tasks_gen/eval/G0008/seed/if_freight_calc.intf.abap create mode 100644 tasks_gen/eval/G0008/spec.md create mode 100644 tasks_gen/eval/G0008/task.json create mode 100644 tasks_gen/eval/G0009/generation.json create mode 100644 tasks_gen/eval/G0009/hidden/t02_hidden.clas.abap create mode 100644 tasks_gen/eval/G0009/reference/shipping_cost_calc.clas.abap create mode 100644 tasks_gen/eval/G0009/reference/shipping_cost_calc.testclasses.abap create mode 100644 tasks_gen/eval/G0009/seed/if_shipping_cost.intf.abap create mode 100644 tasks_gen/eval/G0009/seed/shipping_cost_calc.clas.abap create mode 100644 tasks_gen/eval/G0009/seed/shipping_cost_legacy.clas.abap create mode 100644 tasks_gen/eval/G0009/spec.md create mode 100644 tasks_gen/eval/G0009/task.json create mode 100644 tasks_gen/eval/G0010/generation.json create mode 100644 tasks_gen/eval/G0010/hidden/t02_hidden.clas.abap create mode 100644 tasks_gen/eval/G0010/reference/stock_cover_checker.clas.abap create mode 100644 tasks_gen/eval/G0010/reference/stock_cover_checker.testclasses.abap create mode 100644 tasks_gen/eval/G0010/seed/if_stock_cover.intf.abap create mode 100644 tasks_gen/eval/G0010/spec.md create mode 100644 tasks_gen/eval/G0010/task.json create mode 100644 tasks_gen/eval/G0011/generation.json create mode 100644 tasks_gen/eval/G0011/hidden/t14_hidden.clas.abap create mode 100644 tasks_gen/eval/G0011/reference/cl_repl.clas.abap create mode 100644 tasks_gen/eval/G0011/reference/repl_calc.func.abap create mode 100644 tasks_gen/eval/G0011/reference/t14_test.clas.abap create mode 100644 tasks_gen/eval/G0011/seed/repl_sample.func.abap create mode 100644 tasks_gen/eval/G0011/spec.md create mode 100644 tasks_gen/eval/G0011/task.json create mode 100644 tasks_gen/eval/G0012/generation.json create mode 100644 tasks_gen/eval/G0012/hidden/t14_hidden.clas.abap create mode 100644 tasks_gen/eval/G0012/reference/prd_scrap.func.abap create mode 100644 tasks_gen/eval/G0012/reference/t14_test.clas.abap create mode 100644 tasks_gen/eval/G0012/seed/prd_sample.func.abap create mode 100644 tasks_gen/eval/G0012/spec.md create mode 100644 tasks_gen/eval/G0012/task.json create mode 100644 tasks_gen/eval/G0013/generation.json create mode 100644 tasks_gen/eval/G0013/hidden/t21_hidden.clas.abap create mode 100644 tasks_gen/eval/G0013/reference/repl_qty.func.abap create mode 100644 tasks_gen/eval/G0013/reference/t21_test.clas.abap create mode 100644 tasks_gen/eval/G0013/seed/repl_qty.func.abap create mode 100644 tasks_gen/eval/G0013/seed/repl_qty_old.func.abap create mode 100644 tasks_gen/eval/G0013/spec.md create mode 100644 tasks_gen/eval/G0013/task.json create mode 100644 tasks_gen/eval/G0014/generation.json create mode 100644 tasks_gen/eval/G0014/hidden/t15_hidden.clas.abap create mode 100644 tasks_gen/eval/G0014/reference/stock_bal.prog.abap create mode 100644 tasks_gen/eval/G0014/seed/movmt.tabl.asabap create mode 100644 tasks_gen/eval/G0014/seed/t15_seed.clas.abap create mode 100644 tasks_gen/eval/G0014/spec.md create mode 100644 tasks_gen/eval/G0014/task.json create mode 100644 tasks_gen/eval/G0015/generation.json create mode 100644 tasks_gen/eval/G0015/hidden/bill_age_test.clas.abap create mode 100644 tasks_gen/eval/G0015/reference/bill_age.prog.abap create mode 100644 tasks_gen/eval/G0015/seed/bill.tabl.asabap create mode 100644 tasks_gen/eval/G0015/seed/bill_seed.clas.abap create mode 100644 tasks_gen/eval/G0015/spec.md create mode 100644 tasks_gen/eval/G0015/task.json create mode 100644 tasks_gen/eval/G0016/generation.json create mode 100644 tasks_gen/eval/G0016/hidden/t15_hidden.clas.abap create mode 100644 tasks_gen/eval/G0016/reference/stock_net.prog.abap create mode 100644 tasks_gen/eval/G0016/seed/move.tabl.asabap create mode 100644 tasks_gen/eval/G0016/seed/seed_move.clas.abap create mode 100644 tasks_gen/eval/G0016/seed/stock_net.prog.abap create mode 100644 tasks_gen/eval/G0016/seed/stock_net_old.prog.abap create mode 100644 tasks_gen/eval/G0016/spec.md create mode 100644 tasks_gen/eval/G0016/task.json create mode 100644 tasks_gen/eval/G0017/generation.json create mode 100644 tasks_gen/eval/G0017/hidden/t21_hidden.clas.abap create mode 100644 tasks_gen/eval/G0017/reference/i_proj_effort.ddls.asddls create mode 100644 tasks_gen/eval/G0017/reference/t21_test.clas.abap create mode 100644 tasks_gen/eval/G0017/seed/asgn.tabl.asabap create mode 100644 tasks_gen/eval/G0017/seed/proj.tabl.asabap create mode 100644 tasks_gen/eval/G0017/spec.md create mode 100644 tasks_gen/eval/G0017/task.json create mode 100644 tasks_gen/eval/G0018/generation.json create mode 100644 tasks_gen/eval/G0018/hidden/t20_hidden.clas.abap create mode 100644 tasks_gen/eval/G0018/reference/i_customer.ddls.asddls create mode 100644 tasks_gen/eval/G0018/reference/i_order.ddls.asddls create mode 100644 tasks_gen/eval/G0018/reference/t20_test.clas.abap create mode 100644 tasks_gen/eval/G0018/seed/cust.tabl.asabap create mode 100644 tasks_gen/eval/G0018/seed/ord.tabl.asabap create mode 100644 tasks_gen/eval/G0018/seed/seed_data.clas.abap create mode 100644 tasks_gen/eval/G0018/spec.md create mode 100644 tasks_gen/eval/G0018/task.json create mode 100644 tasks_gen/eval/G0019/generation.json create mode 100644 tasks_gen/eval/G0019/hidden/t20_hidden.clas.abap create mode 100644 tasks_gen/eval/G0019/reference/i_sales_class.ddls.asddls create mode 100644 tasks_gen/eval/G0019/reference/t20_test.clas.abap create mode 100644 tasks_gen/eval/G0019/seed/sale.tabl.asabap create mode 100644 tasks_gen/eval/G0019/spec.md create mode 100644 tasks_gen/eval/G0019/task.json create mode 100644 tasks_gen/eval/G0020/generation.json create mode 100644 tasks_gen/eval/G0020/hidden/t20_hidden.clas.abap create mode 100644 tasks_gen/eval/G0020/reference/i_book_revenue.ddls.asddls create mode 100644 tasks_gen/eval/G0020/reference/t20_test.clas.abap create mode 100644 tasks_gen/eval/G0020/seed/book.tabl.asabap create mode 100644 tasks_gen/eval/G0020/seed/sale.tabl.asabap create mode 100644 tasks_gen/eval/G0020/spec.md create mode 100644 tasks_gen/eval/G0020/task.json create mode 100644 tasks_gen/eval/G0021/generation.json create mode 100644 tasks_gen/eval/G0021/hidden/t16_hidden.clas.abap create mode 100644 tasks_gen/eval/G0021/reference/i_shipment.ddls.asddls create mode 100644 tasks_gen/eval/G0021/reference/t16_test.clas.abap create mode 100644 tasks_gen/eval/G0021/seed/rail.tabl.asabap create mode 100644 tasks_gen/eval/G0021/seed/road.tabl.asabap create mode 100644 tasks_gen/eval/G0021/seed/seed_ship.clas.abap create mode 100644 tasks_gen/eval/G0021/spec.md create mode 100644 tasks_gen/eval/G0021/task.json diff --git a/CLAUDE.md b/CLAUDE.md index d1c9384..86c3578 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,8 +1,15 @@ # ABAP LLM harness — instructions for Claude Code -Talk to the user (Kral) in Turkish. Keep answers short; remove words that add no value. -When you write English text (specs, prompts, docs for the model), use ASD-STE100 Simplified Technical English. -Ask before an action that uses much cloud budget. +## 0. Working with Kral + +- Talk in Turkish. Short answers. Remove words and sentences that add no value. +- English text (specs, prompts, docs for the model): ASD-STE100 Simplified Technical English. +- Ask before an action that costs much (cloud budget) or that changes the A4H system outside harness runs. +- For a series of updates to one document, deliver the result directly as a Markdown file; do not ask each time. +- Record each decision with its date in `docs/yol-haritasi.md` or `docs/faz1-tasarim.md`. +- Commit the current changes before a new generation or test batch. +- Kral has no time for a full review. Eval review = automatic gates + empirical filter (2–3 models) + + Claude review with a checklist; Kral reviews only flagged tasks + one per category (~10 tasks). ## 1. Project @@ -12,6 +19,12 @@ Ask before an action that uses much cloud budget. - Contract of the model: "ABAP task as text + generic ABAP MCP interface". The plan format (plan-agent.md) gives the best result, but it is not mandatory. - Training data: never Claude output. Eval tasks never go into training data. +- Input: the plan format gives the best result but is not mandatory. The old playbook format is not used. +- Generic ABAP MCP interface (core: search_object, read_source, object_structure, where_used, create_object, + write_source, activate, syntax_check, run_unit_tests, run_atc). Small tool name/schema variation in training. +- Category K: incomplete or free-text input + other tool schema. +- Object type mix: CLAS/INTF 35 %, FUNC 15 %, PROG 10 %, DDIC 10 %, CDS 25 %, MSAG + exception 5 %. +- Teacher and task author: DeepSeek V4.1 Flash (Ollama cloud). Budget calendar in `docs/yol-haritasi.md`. - Read first: `docs/yol-haritasi.md` (roadmap, current position) and `docs/faz1-tasarim.md` (design). When a decision or a result changes, update these files. @@ -76,21 +89,27 @@ python3 -c "from harness.ledger import spent; print(spent())" - Done: harness on Mac mini; test include creation in the server; teardown; FUNC/PROG/DDLS support; task generator; ledger and budget guard. - Results: T01 oracle 100, Qwen 27B 41.7 (old run 103), DeepSeek 98.5. T13 DeepSeek 85, T14 DeepSeek 100. -- Running: pilot (`harness.pilot`, 20 tasks) → `runs/gen/pilot.json`, log `runs/gen/pilot.log`. - A macOS notification shows when it ends. -- First generated task G0001: not accepted after 3 attempts (activation error, seed save error, - reference failed 1 hidden test). Feedback now includes the reference write/activation errors. +- Pilot (G0002–G0021) done: 16/20 accepted, 0.16 USD per accepted task (`runs/gen/pilot.json`, + analysis in `docs/faz1-tasarim.md` 11e). Generator improved after the pilot: static checks + (name length, seed type, reserved words, contract/test classes), local abaplint parser check before SAP, + max_tokens 100k, G2 detail in the repair feedback. Runner: G2 works for CDS views with parameters. + The improved generator is not yet tested with new tasks. +- G0020 (rejected) passes with oracle 100 after one fix (remove parameter `default`). G0013 (accepted) has a test + class in its contract: fix in review. ## 7. Next steps -1. Analyze the pilot: acceptance rate, failure causes, cost per task. Improve the generator prompt. +1. Test the improved generator with a small batch (~5 tasks, ~0.7 USD); compare repairs per task with the pilot (2.75 calls). 2. Step D: mutation check (hidden tests must fail on a broken reference). 3. Step F: eval set of 110 tasks: ~150 candidates, empirical filter (2–3 models), Claude review with a checklist, Kral spot-checks ~10 flagged tasks. 4. Scoring for stop tasks (category H) and category K (free-text input, other tool schema). 5. Step 1.3b: generic ABAP MCP interface (spec `abap-mcp-arayuz.md`, later). The proxy is the first adapter. -## 8. Open items for Kral (server) +## 8. Open items for Kral (EPOD server) +- When a write fails, run a syntax check and return its messages. Now the server says only "save failed" + (for example for `TYPE c LENGTH n` in a method signature). The model cannot see the cause and cannot repair; + this hurts eval and training. Workaround in the generator: local abaplint parser check. - Concurrency: queue calls per RFC connection, or use a connection pool. - BDEF creation (needed for RAP tasks). diff --git a/docs/faz1-tasarim.md b/docs/faz1-tasarim.md new file mode 100644 index 0000000..10d282f --- /dev/null +++ b/docs/faz1-tasarim.md @@ -0,0 +1,603 @@ +# Faz 1 Tasarım — ABAP Danışman Modeli: Eval Seti, Görev Formatı, Harness + +Durum: taslak v23 · 2026-10-02 · v23: Adım B — FM, rapor, CDS desteği; T13–T15; eşzamanlılık bulgusu. + +## 1. Amaç + +Faz 1 iki şey üretir: + +1. **Eval seti:** A4H'da otomatik puanlanan ABAP görevleri. Hedef 100 görev; bu dokümanda 10 örnek. +2. **Harness:** Bir modeli EPOD ADT MCP ile A4H'ya bağlar, görevi çalıştırır, puanlar, yörüngeyi kaydeder. + +Aynı harness sonraki fazlarda eğitim verisi üretir: başarılı yörüngeler SFT verisi, puanlar RL ödülü olur. + +## 2. Rol sınırı + +**Modelin sözleşmesi (karar, 2026-10-02):** *Metin olarak ABAP görevi + genel ABAP MCP arayüzü.* Model belirli bir plan formatına veya belirli bir server'ın tool isimlerine bağlı değildir. + + +`plan-agent.md` bugün implementer'ı "ABAP idiomlarını güvenilir hatırlamaz" diye tanımlıyor. Bu yüzden zanaat kararlarını planner veriyor. Yeni düzende bu kararlar modele geçer. + +| Konu | Spec (fonksiyonel taraf / planner) | Model (ABAP danışmanı) | +|---|---|---| +| Goal, iş kuralları, alan eşlemesi | ✓ | | +| Open questions | ✓ | Eksik görürse durur, sorar | +| Context: obje adı, imza, DDIC | ✓ (kısmi olabilir) | Eksikse ADT ile okur | +| Public kontrat (sınıf adı, public metot imzası, hata davranışı) | ✓ | Değiştirmez | +| Zanaat kararları: tablo tipi, key, erişim yolu, SELECT stratejisi, exception tasarımı, release'e uygun syntax | | ✓ | +| Private tasarım, yardımcı metotlar, testler | | ✓ | +| Kod, aktivasyon, doğrulama döngüsü | | ✓ | + +**Kritik kural:** Public kontrat spec'te sabittir. Gizli testler bu kontrata bağlanır. İç tasarım tamamen modelindir. + +## 3. Spec formatı (modelin girdisi) + +`plan-agent.md` yapısı temel alınır, dil STE100 kalır. Değişiklikler: + +- **Decisions** bölümü kaldırılır. Sadece iş kararları kalır ("Business rules"). +- **Steps** iş seviyesine çekilir. Obje sırası ve zanaat detayı modele bırakılır. +- **Contract** ayrı bölüm olur. Gizli testlerin bağlandığı yer burası. +- **Constraints** eklenir. İçeriği: + - Hedef release, paket, isim öneki. + - Coding standards: projeye özgü, denetlenebilir kurallar. + - Out of scope: dokunulmayacak objeler ve eklenmeyecek davranışlar. + +Bölümler, bu sırayla: + +1. Goal +2. Open questions (pozitif görevlerde boş) +3. Context +4. Contract +5. Business rules +6. Constraints +7. Acceptance + +**Plan formatının rolü (karar, 2026-10-02):** `plan-agent.md` formatı zorunlu girdi değil, **en iyi sonuç veren** girdidir. Model serbest metin ve eksik görev tanımıyla da çalışabilmelidir. Görevlerin çoğu bu formatta yazılır; kategori K (bölüm 8) serbest ve eksik girdiyi ölçer. Kral'ın kendi akışında (`/sapplan` → build) plan kullanımı değişmez. + +**Coding standards örneği** (her görevde değişebilir): + +- No comment restates what the code does. +- No Given/When/Then comment header in a test. +- Method length stays below 40 statements. +- No new global variable. +- No method chaining (release kısıtı olan görevlerde). +- Use text elements for user-facing text. + +Bu kuralların çoğu abaplint ile denetlenir (`method_length`, `global_class`, syntax version). Kalanlar özel AST kontrolüdür. + +## 4. Model çıktısı + +Model şunları üretir: + +1. **Decisions bloğu** (STE100): `plan-agent.md` Decisions listesindeki maddeler. Release, tablo tipi ve key, erişim yolu, loop dışı SELECT, exception sınıfı, yeni objelerin adı ve paketi. Her biri tek satır gerekçeyle. +2. **Objeler:** A4H'da oluşturulmuş ve aktif. +3. **Kendi testleri:** ABAP Unit, modelin kendi yazdığı. +4. **ATC kararları:** Her P1/P2 bulgusu ya düzeltilir ya da tek satır gerekçeyle bırakılır. False positive belgelenir. +5. **Rapor** (STE100): `build-agent.md` Reporting kuralı. Her adım, doğrulama sonucu, sapmalar. +6. **Change summary tablosu:** Object · Type · Action · Reason. Throwaway objeler (`$TMP` smoke sınıfı vb.) ayrı işaretlenir. + +Davranış kuralları `build-agent.md`'den uyarlanır: + +- Public kontratı değiştirme. Değiştirmen gerekiyorsa dur ve raporla. +- İş kuralı eksik veya çelişkiliyse dur ve sor. Tahmin etme. +- Zanaat kararı spec'te yoksa **kendin ver**. (Eski kural: "dur ve sor". Bu kural tersine döner.) +- Spec'te istenmeyen davranış ekleme. +- Constraints altındaki out of scope listesine dokunma. +- Aynı doğrulama iki kez başarısız olursa dur ve raporla. Üçüncü deneme yok. +- ABAP Unit'in kapsayamadığı bir davranışı (ör. factory'nin runtime çözümü) `$TMP` smoke objesiyle doğrula. Smoke objesi teslimata girmez. +- Testler gerçek veritabanını okumaz. DB erişimi `CL_OSQL_TEST_ENVIRONMENT` veya `CL_CDS_TEST_ENVIRONMENT` ile izole edilir. + +## 5. Ortam + +**Sistem:** A4H, Mac mini üzerinde Docker. + +**Sistem kimliği:** MCP server'da tek sistem bağlı: `epodabap`, mod `write`. SAP_BASIS 816 SP01. Modern syntax'ın tamamı mevcut; eski release hedefleri sadece abaplint ile denetlenir. + +**İzolasyon:** MCP server paket (DEVC) oluşturamaz. Bu yüzden her koşu `$TMP` içinde çalışır, objeler koşuya özel önekle adlanır. Önek şeması: `Z_` (ör. `Z007003_DISC_CALC`). Önek her koşuda yenidir; eski koşunun objeleriyle çakışma olmaz. Spec ve gizli testler isimleri yer tutucuyla taşır (`{{P}}DISCOUNT_CALC`); harness koşu başında doldurur. 30 karakter sınırı görev yazımında kontrol edilir. + +**Seed verisi:** MCP server'da tablo yazma tool'u yok (`sap_sql_query` sadece okur). Harness, seed verisini `IF_OO_ADT_CLASSRUN` uygulayan bir seed sınıfı ile yükler ve `sap_run_class` ile çalıştırır. + +**Teardown:** Harness, koşu sonunda objeleri ADT'nin kendi silme API'si ile siler. MCP server değişmez, model bu yolu görmez. + +``` +POST /sap/bc/adt/deletion/delete +Accept: application/vnd.sap.adt.deletion.response.v1+xml +Content-Type: application/vnd.sap.adt.deletion.request.v1+xml + + + + + + +``` + +- Yanıt her obje için `del:isDeleted="true|false"` ve mesaj döner. DDIC objelerinde silme log linki gelir. +- Obje URI'leri `sap_search_object` sonucundaki `uri` alanından alınır (önekle arama). +- `$TMP` objelerinde `transportNumber` boş kalır. +- Harness, CSRF token'ı (`x-csrf-token: fetch`) ve oturum cookie'sini yönetir. + +**Doğrulandı (2026-10-02, Mac mini):** 3 bağımlı obje (interface, onu implemente eden sınıf, test sınıfı) tek istekte silindi. Toplam 20 objeden 19'u silindi. Kalan 1 obje (`Z002001_IBAN_VALIDATOR`) "You are already editing" ile reddedildi: başka bir oturumda (muhtemelen MacBook'taki ADT) açık kilit. Harness bunu `deleted: false` olarak raporluyor; kilit kalkınca `teardown` tekrar çalıştırılır. + +Önceki doğrulama listesi (tarihsel): +- Bir istekte birden çok `del:object` kabul ediliyor mu? +- Bağımlı objeler (ör. sınıf + kullandığı tablo tipi) aynı istekte doğru sırayla siliniyor mu? Değilse harness sırayı verir: test sınıfı → sınıf → interface → CDS → tablo tipi → yapı/tablo → data element → domain → mesaj sınıfı. +- `isDeleted="false"` dönen obje: harness loglar, koşuyu "kirli" işaretler, sonraki koşu yine benzersiz önekle başlar. + +**Sızıntı koruması** (silme başarısız olursa diye, ek güvenlik): Her koşunun öneki benzersizdir. Harness, modelin `sap_search_object` ve `sap_usage_references` sonuçlarından başka koşuların öneklerini filtreler. + +**Paralellik (test edildi, 2026-10-01):** MCP server en fazla **8 eşzamanlı oturum** tutuyor. Bu sınır bilinçli: A4H ve ADT'nin taşıyabileceği yük kadar. 9. oturum `initialize`'da ID alıyor ama ilk çağrıda HTTP 404 dönüyor. 8 paralel syntax check 1–2 saniyede bitti. Toplam kapasite 8 oturumla sınırlı; Adım 3 ve 5 (binlerce koşu) için süre hesabı buna göre yapılır. (Karşılaştırma: normal geliştirme sistemlerinde 12 agent aynı anda çalışabiliyor; kısıt A4H'nın kaynakları.) + +**Makine dağılımı (v13: her şey Mac mini'de):** + +Yük testi, 2026-10-02, Mac mini M4 Pro 64 GB, A4H çalışırken Qwen 3.8 27B (MLX, nvfp4), `num_ctx` 32k: + +| Ölçüm | Değer | +|---|---| +| A4H bellek (üretim sırasında) | 28,9 GB (Docker limiti 62,7 GB) | +| Sistem boş bellek / swap | %49 / 0,5 GB — baskı yok | +| A4H tepkisi | Etkilenmedi | +| Prompt eval | 119 token/s | +| Üretim (eval) | 31 token/s | +| Test prompt'u (T01 benzeri, tek sınıf + testler) | 11 678 token, 6 dk 18 sn | + +Sonuç: A4H, MCP server, harness ve model Mac mini'de birlikte çalışır. MacBook serbest kalır. + +- 11 678 token'ın çoğu muhtemelen düşünme (thinking) çıktısı. Ölçümde iki mod da denenir: thinking açık ve kapalı. Süre ve puan farkı baz model seçimine girer. +- Kaba süre tahmini: görev başına 15 tur × ~2 000 token ≈ 16 dk üretim + tool süresi → 20–30 dk/görev. 100 görev ≈ 35–50 saat/model. Mac mini 7/24 koşarsa model başına ~2 gün. +- Opsiyonel koruma: Docker Desktop bellek limitini ~34 GB'a indirmek; HANA büyüyüp modelin alanına girmesin. +- Adım 3'te kapasite yetmezse: ikinci A4H (kiralık VM) veya modeli kiralık GPU'da koşturmak. + +Harness kuralları: +- Yerel model (MacBook): **tek worker**, paralel istek yok. Paralel worker (en fazla 6, 2 oturum senin kullanımın için) sadece model uzakta çalışıyorsa (kiralık GPU). +- Her worker koşu boyunca tek oturum kullanır, koşu sonunda oturumu `DELETE` ile kapatır. +- 404 alan çağrı: oturumu yeniden aç, çağrıyı bir kez tekrarla, koşuyu "kirli" işaretle. + +Opsiyonel server iyileştirmesi: sınır aşıldığında `initialize` aşamasında açık hata (429/503) dönmek. Böylece harness oturumu almadan bekler. Paralel **yazma ve aktivasyon** henüz test edilmedi; Adım 1.3'te ölçülecek. + +**Genel ABAP MCP arayüzü (karar, 2026-10-02).** Model EPOD tool isimlerini değil, genel bir "ABAP MCP" arayüzünü görür. Ayrıntılı spec: `abap-mcp-arayuz.md` (sonra yazılacak). + +| Genel tool | EPOD karşılığı (ilk adapter) | +|---|---| +| `search_object` | `sap_search_object` | +| `read_source` | `sap_pull_source` | +| `object_structure` | `sap_object_structure`, `sap_object_members` | +| `where_used` | `sap_usage_references` | +| `create_object` | `sap_create_object` | +| `write_source` | `sap_push_source`, `sap_push_element` | +| `activate` | `sap_activate` | +| `syntax_check` | `sap_syntax_check` | +| `run_unit_tests` | `sap_run_unit_test`, `sap_check_object` (coverage) | +| `run_atc` | `sap_atc_run` | + +- EPOD bu arayüzü native destekleyecek. O zamana kadar harness proxy'si genel arayüzü EPOD tool'larına çeviren **ilk adapter**dir. +- **Delete/teardown arayüzde yok.** Harness'e aittir (ADT deletion API). +- Çekirdek dışındaki EPOD tool'ları (`sap_sql_query`, `sap_run_class`, `sap_short_dumps`, `sap_element_info`, `sap_inactive_objects`, `sap_pretty_print`, `sap_push_message`) için genel karşılık spec'te kararlaştırılacak. +- **Eğitimde az miktarda tool isim ve şema varyasyonu** kullanılır (ör. `read_source` / `get_source`, parametre adlarında farklılık). Amaç: model belirli bir isim setine ezber yapmasın. + +**Tool seti (server v2.3.0, 33 tool).** Model sadece aşağıdaki beyaz listeyi görür. Server tool'larında `readOnlyHint` anotasyonu yok; bu yüzden ayrım harness'te beyaz listeyle yapılır. + +| Grup | Model görür | Model görmez | +|---|---|---| +| Okuma | `sap_search_object`, `sap_pull_source`, `sap_object_structure`, `sap_object_members`, `sap_usage_references`, `sap_element_info`, `sap_inactive_objects`, `sap_short_dumps`, `sap_sql_query` | `sap_adt_raw_get`, `sap_list_systems`, `sap_compare_systems`, `sap_object_versions`, `sap_object_diff`, `sap_pull_source_to_file` | +| Yazma | `sap_create_object`, `sap_push_source`, `sap_push_element`, `sap_push_message`, `sap_activate` | `sap_push_source_from_file`, `sap_lock`, `sap_unlock` | +| Kalite | `sap_syntax_check`, `sap_check_object` (`runUnitTest`, `coverage`), `sap_run_unit_test`, `sap_atc_run`, `sap_pretty_print` | | +| Çalıştırma | `sap_run_class` (`$TMP` smoke için) | | +| Transport | | `sap_transport_list`, `sap_transport_of_object`, `sap_transport_create`, `sap_transport_release` | + +Notlar: +- `sap_create_object` 16 tip oluşturur: PROG, CLAS, INTF, FUGR, FUNC, DTEL, DOMA, TABL, TTYP, STRU, DDLS, DDLX, SRVD, DCLS, DDLA, MSAG. BDEF oluşturamaz → kategori J (RAP) için server'a BDEF create eklenmeli. +- `sap_check_object` P1/P2 ATC bulgusunda başarısız döner; G5 bununla ölçülür. `coverage=true` sadece global sınıfı ölçer. +- `sap_push_source` varsayılan olarak aktive eder. Birden çok objeli değişiklikte `activate=false` + `sap_activate` sırası modelin öğrenmesi gereken bir beceri. + +**Aktivasyon kontrolü:** Planlarda geçen "MCP activation wedge" nedeniyle harness, her push sonrası aktif versiyonu ayrıca okur. Modelin raporu değil, aktif kaynak puanlanır. + +## 6. Puanlama + +### 6.1 Kapılar (0/1) + +Bir kapı geçilmezse görev puanı 0'dır. + +- G1: Kontrattaki tüm objeler var ve aktif. +- G2: Kontrattaki public imzalar spec ile birebir aynı. +- G3: Gizli testlerin derlenmesi ve en az bir testin geçmesi. +- G4: Out of scope listesindeki hiçbir obje değişmemiş. Harness, seed objelerinin aktif kaynağını koşu öncesi ve sonrası karşılaştırır. +- G5: Açık P1 ATC bulgusu yok. + +### 6.2 Bileşenler (kapılar geçildiyse) + +| Bileşen | Ağırlık | Ölçüm | +|---|---|---| +| Doğruluk | 40 | Gizli ABAP Unit testlerinin geçme oranı | +| Zanaat | 20 | Görevin `craft_checks` listesi (abaplint kuralları + özel AST kontrolleri) | +| Clean ABAP + Coding standards | 15 | abaplint Clean ABAP kuralları + görevin coding standards kontrolleri | +| Modelin testleri | 15 | (a) Hatalı referans implementasyonu yakalama, (b) statement/branch coverage (hedef görevde verilir, ör. %70), (c) gerçek DB erişimi yok | +| Disiplin | 10 | Gerekçesiz P2 bulgusu yok; iki başarısızlık sonrası durma kuralına uyma; change summary eksiksiz ve doğru; tool çağrısı bütçesi | + +Ağırlıklar başlangıç değeri. İlk baz model ölçümünden sonra ayarlanır. + +### 6.3 Negatif görevler + +Beklenen sonuç `stop` olan görevlerde: + +- Model obje oluşturmadan durur ve eksik noktayı adlandırır: 100. +- Model durur ama yanlış noktayı adlandırır: 30. +- Model uygular: 0. + +"Doğru noktayı adlandırdı mı" kontrolü: görevde `expected_gap` anahtar kelimeleri + hakem model. Hakem modeli sadece bu kontrol için kullanılır. + +### 6.4 Release hedefi + +A4H en yeni release'te. Ama gerçek sistemler eski olabilir: senin planlarında "this system does not support method chaining" kısıtı var. Bu yüzden her görev bir `release_target` taşır. abaplint `syntax.version` ayarı (ör. `v702`, `v740sp05`, `v750`) bu hedefi A4H'dan bağımsız denetler. Hedef release'i aşan syntax G1'i düşürür. + +### 6.5 abaplint ve standart tipler (test edildi, 2026-10-01) + +Test: abaplint 2.120.63, abapGit formatında seed objeleri (yapı, tablo tipi, şeffaf tablo) + bunları kullanan bir sınıf. + +| Kontrol | Sonuç | +|---|---| +| Seed tiplerini çözme (alan seviyesine kadar) | ✓ `ls_order-amountx` → "Component not found" | +| Eksik Z tipi | ✓ `ZEVAL_TT_MISSING` → `unknown_types` | +| Standart tipler (`TIMESTAMPL`, `SYUNAME`, `WAERS`, `CL_ABAP_TSTMP`) | Yanlış alarm yok (`errorNamespace: ^(Z|Y|LCL_|TY_|LIF_)`) | +| Standart sınıfta yanlış parametre adı | ✗ Yakalanmıyor (beklenen) | +| `db_operation_in_loop` | ✓ | +| Release hedefi v702: inline `DATA(...)` | ✓ Hata | + +Sonuç ve iş bölümü: +- **A4H syntax check / aktivasyon = doğruluk.** Standart API kullanımı burada denetlenir. +- **abaplint = release hedefi, zanaat, stil.** Standart tipleri bilmesi gerekmez. +- Seed objeleri abapGit formatında tutulur; abaplint onları doğrudan okur. Aynı dosyalar A4H'ya MCP ile kurulur. +- Not: v702 hatalarında zincirleme (cascade) hatalar oluşuyor. Release kapısı için hata sayısı değil, "en az bir `check_syntax` hatası var mı" ölçülür. + +## 7. Görev şeması + +```yaml +id: T03 +version: 1 +category: C # bkz. bölüm 8 +difficulty: 2 # 1-3 +release_target: v750 +expected_outcome: implement # implement | stop +budget: + max_tool_calls: 60 + max_activations: 15 +spec: spec.md # bölüm 3 formatında, STE100 +seed: # görev başında A4H'ya kurulur + objects: seed/ # abapGit formatında DDIC, sınıflar + data: seed/data.json # tablo içerikleri + faulty_reference: seed/ref_faulty/ # modelin testlerini ölçmek için +hidden_tests: hidden/ # global test sınıfı, kontrata bağlı +craft_checks: + - abaplint: db_operation_in_loop + - custom: lookup_table_is_sorted_or_hashed +expected_gap: [] # sadece stop görevlerinde +``` + +Gizli testler global test sınıfıdır (`FOR TESTING`). Modelin sınıfına sadece public kontrat üzerinden erişir. Bu yüzden G2 (imza eşleşmesi) kapıdır. + +## 8. Kategoriler + +| Kod | Kategori | Ölçülen beceri | +|---|---|---| +| A | Saf mantık, yeni sınıf | Dil, OO, Clean ABAP | +| B | Veritabanı erişimi + test double | Open SQL, CDS, `CL_OSQL_TEST_ENVIRONMENT` | +| C | Internal table zanaatı | Tablo tipi, key, erişim yolu | +| D | Exception tasarımı | Class-based exception, T100 mesajları | +| E | Refactoring | Legacy kodu davranışı koruyarak temizleme | +| F | Bilinmeyen obje | Spec'te imzası olmayan seed objesini ADT ile okuyup kullanma | +| G | Release kısıtı | Eski release'e uygun syntax | +| H | Negatif | Eksik veya çelişkili spec'te durma | +| I | Hata düzeltme | Kırmızı test veya short dump'tan kök nedene | +| J | CDS / RAP | View entity, behavior (sonraki dalga) | +| K | Eksik / serbest girdi + farklı tool şeması | Plan formatı olmadan, serbest metin veya eksik görev tanımıyla çalışma; farklı tool isim ve şemalarına uyum. Eksik bilgi kritikse durup sorma (H ile ilişkili) | + +Hedef dağılım (beceri kategorisine göre): A 10, B 15, C 15, D 10, E 15, F 10, G 10, H 10, I 5, **K 10** (toplam 110). J ikinci dalgada. K görevleri diğer kategorilerin görevlerinin serbest metin veya farklı şemalı varyantları olarak üretilir. + +### 8.1 İkinci boyut: obje tipi + +Beceri kategorisi tek başına yetmez. T01–T12'nin hepsi sınıf; gerçek işte function module, rapor ve DDIC de var. Her görev bir beceri kategorisi **ve** bir ana obje tipi taşır. + +| Obje tipi | Hedef pay | MCP ile mümkün mü | Gizli test yöntemi | +|---|---|---|---| +| CLAS / INTF | ~35 % | ✓ | Global test sınıfı, public kontrat üzerinden | +| FUGR / FUNC | ~15 % | ✓ (`sap_create_object` FUGR + FUNC) | Global test sınıfı `CALL FUNCTION` ile çağırır; exception'lar `EXCEPTIONS` ile kontrol edilir | +| PROG (rapor, selection screen, ALV) | ~10 % | ✓ | `SUBMIT ... WITH ... AND RETURN` + `cl_salv_bs_runtime_info=>set( display = abap_false data = abap_true )` ile ALV verisi yakalanır; WRITE listeleri `EXPORTING LIST TO MEMORY` + `LIST_FROM_MEMORY` ile | +| DDIC (TABL, STRU, DTEL, DOMA, TTYP) | ~10 % | ✓ | Yapı kontrolü: `sap_object_structure` / `DD03L`, `DD04L`, `DD01L` sorguları; kullanan kodun derlenmesi | +| CDS (DDLS, DCLS, DDLX, SRVD) | ~25 % | ✓ (BDEF hariç) | `CL_CDS_TEST_ENVIRONMENT` ile test double; sonuç kümesi karşılaştırması | +| MSAG + exception | ~5 % | ✓ (`sap_push_message`) | T100 mesaj metni ve parametre kontrolü | + +**CDS alt konuları** (25 görev civarı): view entity ve join'ler, association ve path expression, aggregation ve `GROUP BY`, `CASE` / cast / built-in fonksiyonlar, parametreli view, access control (DCLS), annotation ve metadata extension (DDLX), table function + AMDP sınıfı, service definition (SRVD), ABAP'tan CDS tüketimi (Open SQL ile association kullanımı). RAP (BDEF) server'a BDEF oluşturma eklenince J kategorisinde. + +Kapsam dışı (ADT/MCP ile yapılamıyor): dynpro (`CALL SCREEN`), SmartForms, BAdI/enhancement implementasyonu, IDoc tanımı. Gerçek işte var ama A4H + MCP ile doğrulanamıyor. + +**Gerçek işten uyarlanabilir desen:** FI-CA event modülleri gibi "sabit arayüzlü FM" işleri. Seed'de örnek bir FM (sabit imza) verilir; görev aynı imzayla yeni bir FM yazmaktır. Bu, F kategorisini (bilinmeyen objeyi okuyup taklit etme) gerçek bir desenle ölçer. + +**Release ile ilişki:** Eski release hedefli görevler (G kategorisi) doğal olarak FM ve rapor ağırlıklı olur: 7.02'de sınıf yerine FORM rutinleri ve FM'ler yaygın. + +**Harness'te gereken değişiklikler:** +- G2 (kontrat): FUNC için imza kontrolü (`FUNCTION ... IMPORTING/EXPORTING/TABLES/EXCEPTIONS` bloğu), PROG için selection screen parametreleri. +- abaplint dışa aktarımı: şu an sadece CLAS/INTF. PROG ve FUGR abapGit formatı eklenecek. +- Teardown sırası: FUNC → FUGR, PROG. +- Seed kurulumu: FUGR + FUNC (FM önce grup ister). + +**Sıradaki görevler:** T13 (FUNC, sabit arayüzlü FM, kategori F) ve T14 (PROG, selection screen + ALV, kategori B). + +## 9. Örnek görevler + +### 9.1 Tam örnek: T03 (kategori C) + +**Seed:** `ZEVAL_DISC_RULE` tablosu (`CUST_GROUP` CHAR4 key, `MIN_AMOUNT` CURR, `DISCOUNT_PCT` DEC 5,2), 200 satır. `ZEVAL_S_ORDER` yapısı ve `ZEVAL_TT_ORDER` tablo tipi. + +**spec.md:** + +```markdown +# 1. Goal +Calculate the discount for each order line. The sales team uses the result in a +nightly batch run with up to 500 000 order lines. + +# 2. Open questions +None. + +# 3. Context +- Table ZEVAL_DISC_RULE, package ZEVAL_T03. Key: CUST_GROUP. + Fields: CUST_GROUP (CHAR4), MIN_AMOUNT (CURR 15,2), DISCOUNT_PCT (DEC 5,2). + One customer group has one or more rules. +- Table type ZEVAL_TT_ORDER, line type ZEVAL_S_ORDER. + Fields: ORDER_ID (CHAR10), CUST_GROUP (CHAR4), AMOUNT (CURR 15,2), + CURRENCY (CUKY). + +# 4. Contract +- Create the class ZCL_EVAL_DISCOUNT_CALC in the package ZEVAL_T03. +- Public method CALCULATE: + IMPORTING it_orders TYPE zeval_tt_order + RETURNING VALUE(rt_result) TYPE zeval_tt_disc_result +- Create the table type ZEVAL_TT_DISC_RESULT. Line fields: ORDER_ID, + DISCOUNT_PCT, DISCOUNT_AMOUNT (CURR 15,2), CURRENCY. +- The method returns one result line for each order line, in the input order. + +# 5. Business rules +- For an order line, use the rule of the same customer group with the highest + MIN_AMOUNT that is less than or equal to AMOUNT. +- If no rule applies, the discount is 0. +- DISCOUNT_AMOUNT is AMOUNT multiplied by DISCOUNT_PCT divided by 100. Round + half up to 2 decimals. + +# 6. Constraints +- Release target: 7.50. +- Do not change ZEVAL_DISC_RULE. + +# 7. Acceptance +- The hidden tests pass. +- The class runs 500 000 order lines without a database access in a loop. +``` + +**Gizli testler:** kural yok, tek kural, sınır değer (`AMOUNT = MIN_AMOUNT`), çoklu kural, yuvarlama, boş girdi, sıra korunumu. + +**craft_checks:** `db_operation_in_loop` (abaplint); özel: kural tablosu `SORTED` veya `HASHED`, ya da tek `SELECT` + sıralı okuma; loop içinde `LOOP AT ... WHERE` üzerinde standart tablo yok. + +**faulty_reference:** Sınır değerde `<` yerine `<=` hatası olan implementasyon. Modelin testleri bunu yakalamalı. + +### 9.2 Kısa örnekler + +| ID | Kat. | Release | Görev özeti | Beklenen zanaat / davranış | +|---|---|---|---|---| +| T01 | A | 7.58 | IBAN biçim doğrulayıcı sınıf; mod-97 kontrolü, hata nedenleri enum olarak | Saf fonksiyonlar, `ENUM` veya sabitler, küçük metotlar | +| T02 | B | 7.58 | Seed tablosundan açık kalemleri yaşlandırma bantlarına göre toplayan sınıf | Tek aggregate `SELECT`, `CL_OSQL_TEST_ENVIRONMENT` ile test | +| T03 | C | 7.50 | Yukarıdaki tam örnek | Loop dışı SELECT, sorted/hashed lookup | +| T04 | D | 7.58 | Dosya satırı parser'ı; satır ve alan bilgisiyle anlamlı hata | Class-based exception, T100 mesaj sınıfı, `IF_T100_DYN_MSG` | +| T05 | E | 7.58 | 300 satırlık seed rapor (FORM'lar, global data) → sınıfa refactor; mevcut çıktı testle sabit | Davranış korunur, global state yok, testlenebilir yapı | +| T06 | F | 7.58 | Seed'deki `ZCL_EVAL_FX_PROVIDER` ile para birimi çevirimi. Spec sadece sınıf adını verir, imzayı vermez | Model önce ADT ile okur, sonra kullanır | +| T07 | G | 7.02 | T03 benzeri lookup, ama 7.02 hedefi | Inline declaration yok, `NEW`/`VALUE`/`COND` yok, method chaining yok | +| T08 | H | 7.58 | Spec iki çelişkili yuvarlama kuralı içeriyor | `stop`; `expected_gap`: rounding | +| T09 | H | 7.58 | Spec bir alan eşlemesini vermiyor (kaynak alan belirsiz) | `stop`; `expected_gap`: field mapping | +| T10 | I | 7.58 | Seed sınıf büyük girdide `TSV_TNEW_PAGE_ALLOC_FAILED` dump'ı veriyor; dump bilgisi spec'te | Kök neden: gereksiz tablo kopyası; düzeltme + regresyon testi | +| T11 | B+F | 7.50 | Nötr alan: seed'de log tablosu + referans tablosu + factory sınıfı. Görev: iki yönlü reader interface ve sınıfı (anahtardan kayda, kayıttan anahtara), factory'ye `GET_READER` ekle. Belge anahtarı `REF TO data` olarak gelir; iki eşleme dalı | Join + en yeni satır (`ORDER BY ... DESCENDING`, `UP TO 1 ROWS`), cast hatası → anlamlı exception, factory'nin mevcut desenini okuyup taklit etme (F), method chaining yok, OSQL test double, `$TMP` smoke ile factory doğrulaması | +| T12 | H | 7.58 | T11'in varyantı: istenen exception text id'si için gereken attribute mevcut exception sınıfında yok | `stop` veya açık sapma raporu; `expected_gap`: exception attribute | + +## 10. Harness akışı + +``` +1. setup paket oluştur, seed objelerini kur, seed verisini yükle +2. run modeli başlat: system prompt + spec + MCP tool'ları + bütçe: max_tool_calls, max_activations, zaman aşımı +3. collect paketteki tüm objelerin aktif kaynağını çek +4. gate G1 (aktif mi), G2 (imza eşleşmesi) +5. test gizli test sınıfını kur, ABAP Unit koştur +6. check ATC + abaplint + özel craft kontrolleri +7. own-tests modelin testlerini faulty_reference'a karşı koştur +8. score puanı hesapla, JSON olarak yaz +9. log tam yörüngeyi JSONL olarak sakla (SFT adayı) +10. teardown paketi sil +``` + +Harness dili: Python (eğitim ekosistemi aynı dilde). MCP istemcisi olarak EPOD ADT MCP server'ına bağlanır. + +Model arayüzü: OpenAI-uyumlu chat API. Böylece Ollama/MLX ile yerel modeller ve uzak API'ler aynı harness'te koşar. + +## 11. Baz model ölçümü + +Faz 1 sonunda aynı 100 görev şu gruplarla koşar: + +- Aday baz modeller (Apache 2.0, orta boy, agentic). Liste ölçüm öncesi güncel durumla seçilir. +- Referans üst sınır: Claude. Sadece ölçüm için; çıktıları eğitim verisine girmez. + +Sonuç tablosu: kategori bazında puan. Baz model seçimi ve eğitim önceliği bu tablodan çıkar. + +**Hedef çizgi:** Claude referansına tüm kategorilerde en fazla 10 puan geride kalmak. T11 ve T12 bu karşılaştırmanın ana görevleri. + +## 11a. Adım 1.3 sonuçları (2026-10-01) + +**Konum:** `~/projects/abap-llm/harness` (MacBook) + +| Modül | Görev | +|---|---| +| `harness/mcp_client.py` | MCP streamable-http istemcisi; 404'te oturumu yeniden açar | +| `harness/adt_client.py` | ADT deletion API (sadece teardown; kimlik bilgisi `.env`'den) | +| `harness/proxy.py` | Beyaz liste, bütçe, başka koşu öneklerini filtreleme, yazma hata serisi sayacı, yörünge logu | +| `harness/agents.py` | `oracle` (referans çözüm), `null`, `llm` (OpenAI-uyumlu, zaman bütçesi 30 dk) | +| `harness/runner.py` | setup → agent → collect → G1–G6 → gizli testler → kendi testleri → ATC → abaplint → puan → teardown | +| `harness/cli.py` | `run`, `teardown`, `teardown-all`, `cleanup-list` | +| `tasks/T01/` | IBAN görevi: spec, seed interface, 12 gizli test, referans | + +**Koşular (T01):** + +| Koşu | Agent | Puan | Not | +|---|---|---|---| +| 002 | oracle | 85 | 12/12 gizli test, abaplint ve ATC temiz. Kendi testleri 0 (referansta test yok). | +| 003 | null | 0 | G1, G2, G3 düştü. | +| 004 | qwen3.8:27b-mlx | — (32 dk sonra elle durduruldu; erken) | 15 tool çağrısı, 11 yazmadan 10'u aktivasyon hatası. Interface metodunu niteleme, offset syntax'ı (`lv_pos+1`), `find( ... ignore_case )` parametreleri. İki başarısızlıktan sonra durmadı. | + +Oracle ve null, puan aralığının iki ucunu doğruluyor. Koşu 004, eval'in ölçmesi gereken zayıflığı tam gösteriyor: ABAP syntax bilgisi ve durma disiplini. + +**Eklenen kurallar:** +- Yerel model kuralları: zaman limiti yok (sınırı tool çağrısı bütçesi koyar; `--max-minutes` opsiyonel). İstekler tek tek gider: tek worker, `parallel_tool_calls: false`, tool çağrıları sırayla çalışır. +- Disiplin: art arda 3 veya daha fazla başarısız yazma/aktivasyon → disiplin puanından −5. + +**Ölçüm süresi riski:** Yerel 27B model, Ollama'da 262k bağlamla tur başına 2–3 dakika harcadı. 100 görev × 30 dakika ≈ 50 saat/model. Adım 1.6 öncesi: bağlam penceresini sınırlamak (ör. 32k), MLX server'ı denemek, ölçümü gece ve hafta sonu koşularına bölmek. Koşu 005 (sınırsız süre) ilk gerçek süre ölçümünü verecek. + +**MCP server eksiği (test include):** Yeni sınıfta test include yok; ADT artık onu otomatik oluşturmuyor (butona basmak gerekiyor). MCP oluşturamıyor. Karar: server'a eklenecek. Geçiş döneminde model testleri global test sınıfına yazabilir; puanlama ikisini de sayıyor. Eklendikten sonra yeni bir probe sınıfıyla test edilir. + +**Teardown:** Mac mini'de `.env` (A4H kimlik bilgisi) dolduruldu; teardown her koşu sonunda otomatik çalışır. Kilitli 1 obje ve MacBook'taki koşu 005'in objeleri (`Z005001_`) bekliyor; koşu 005 bitmeden silinmez. + +## 11b. Mac mini kurulumu (2026-10-02) + +| Konu | Durum | +|---|---| +| Konum | `~/projects/abap-llm/harness` (kullanıcı `erhankeseli`) | +| Erişim | Bu Project'in sohbetleri Mac mini'deki Claude Desktop'tan; Desktop Commander Mac mini'de | +| MCP server | ADT (Eclipse) içinde, `127.0.0.1:3000`. Sistem adı `A4H`, mod `write` | +| MCP token | ADT plugin ayarından (`com.epod.adt.mcp.plugin.prefs`) `.env`'e yazıldı; `.env` izni 600 | +| Python / Node | Python 3.9.6 (sistem), Node 26.8.2; abaplint 2.120.63 | +| Model | `qwen3.8-27b-32k` = `qwen3.8:27b-mlx` + `num_ctx 32768` (Ollama Modelfile) | +| Bağlam takibi | Her model yanıtının `usage` değeri yörüngeye yazılıyor; 32k sınırına yaklaşma buradan görülür (Ollama sınırı aşınca sessizce keser) | +| Koşu numaraları | MacBook: 001–099, Mac mini: 101+. Önekler çakışmaz. | + +Doğrulama: oracle (koşu 101) **85**, null (koşu 102) **0**. MacBook sonuçlarıyla aynı. + +### Koşu 103: qwen3.8-27b-32k, T01 — **41,7 / 100** + +| Bileşen | Puan | Neden | +|---|---|---| +| Doğruluk | 16,7 / 40 | 12 gizli testten 5'i geçti. Geçerli IBAN'lar OK dönmüyor (checksum hesabı yanlış); COUNTRY ve LENGTH kontrolleri yanlış sırada veya yanlış. | +| Zanaat | 10 / 20 | `method_length`: ana metot 45, yardımcı runner metodu 84 statement. | +| Clean ABAP | 15 / 15 | Bulgu yok. | +| Kendi testleri | 0 / 15 | İki "test" sınıfı yazdı ama `FOR TESTING` yok; biri interface metodunu niteleme hatasıyla derlenmiyor. Gerçek ABAP Unit testi yok. | +| Disiplin | 0 / 10 | Tool bütçesi (40) bitti, 22 aktivasyon (bütçe 10), art arda 5 başarısız yazma. Rapor yazamadan durdu. | + +Süre: 38 dakika (2 269 sn). Yardımcı objeler: bir `IF_OO_ADT_CLASSRUN` runner (smoke için; kurala uygun) ve iki test sınıfı denemesi. + +**Bu koşudan çıkan harness düzeltmeleri:** +- abaplint JSON çıktısında `file` alanı string geliyor; parse düzeltildi (koşu 103 bu yüzden puanlama adımında çökmüştü). +- `rescore` komutu: agent'ı tekrar çalıştırmadan, A4H'daki objelerden ve yörüngeden yeniden puanlar. Puanlayıcı değiştiğinde eski koşular tekrar değerlendirilebilir. +- G6 (release kapısı) sadece kontrat objelerine uygulanır. Yardımcı objeler abaplint'in bilmediği standart API'leri (`IF_OO_ADT_CLASSRUN`) kullanabilir; bunlarda yanlış alarm oluşuyordu. Doğruluk için A4H syntax check yeterli. +- G4 karşılaştırması normalize edildi (boşluk ve büyük/küçük harf). +- Kendi testleri: modelin yazdığı **global test sınıfları** da sayılır (MCP local test include oluşturamadığı için modelin tek yolu bu). + +**Açık puanlama soruları:** +- Yardımcı objelerdeki (runner, smoke) bulgular zanaat puanından düşsün mü? Şu an düşüyor. +- Bütçe bitince rapor yok. Rapor eksikliği ayrıca cezalandırılsın mı, yoksa bütçe aşımı cezası yeterli mi? + +## 11c. Baştan başlangıç (2026-10-02, koşu 201+) + +**Test include çözüldü (server):** `sap_create_object` CLAS ile test include'u da oluşturuyor; `sap_push_source` `includeType: "testclasses"` ile local test yazıyor (include yoksa oluşturuyor). Probe `Z191001_TINC`: 1 test, coverage %50 (beklenen). Global test sınıfı geçici çözümüne artık gerek yok; puanlama ikisini de saymaya devam ediyor. + +**Uygulanan puanlama kararları:** +- Zanaat bulguları sadece kontrat objelerinden sayılır (yardımcı runner/smoke objeleri hariç). +- Model rapor yazmadan biterse (boş veya "Stopped:") disiplinden −5. + +**Referans çözüme local testler eklendi** (T01: 6 test). Oracle artık "kendi testleri" bileşenini de doğruluyor. + +| Koşu | Agent | Puan | Not | +|---|---|---|---| +| 201 | oracle | **100** | 12/12 gizli test; kendi testleri 6/6, coverage %100; teardown 3/3 otomatik | +| 202 | null | **0** | | +| 203 | qwen3.8-27b-32k | (çalışıyor) | | +| 204 | deepseek-v4.1-flash:cloud (Ollama cloud) | **98,5** | 12/12 gizli test; 16 local test, coverage %98; 8 tool çağrısı, 5 aktivasyon; 2,5 dk. Tek bulgu `prefer_xsdbool`. Rapor plan formatında, kararlar gerekçeli. İlk aktivasyon hatasını (string offset) bir denemede düzeltti. | + +Koşu 204 token kullanımı: 8 tur, 104 093 girdi (kümülatif, çoğu cache), 51 830 çıktı (düşünme dahil), en büyük bağlam 16 496. DeepSeek liste fiyatıyla ≈ 0,09 $ / koşu. + +Eski koşular `runs/_archive_v1` altında. + +## 11d. Adım B: FM, rapor ve CDS desteği (2026-10-02) + +**Doğrulanan mekanizmalar (A4H + MCP):** TABL (DDL kaynağı), DDLS, FUGR, FUNC (kaynakta imza), PROG oluşturma ve yazma; seed verisi `IF_OO_ADT_CLASSRUN` sınıfı + `sap_run_class` (`className`); gizli testlerde `CALL FUNCTION` + `EXCEPTIONS`, `SUBMIT ... AND RETURN` + `cl_salv_bs_runtime_info` ile ALV verisi, `CL_CDS_TEST_ENVIRONMENT` ile CDS test double'ları. + +**Harness değişiklikleri:** +- Kurulum: FUGR (kaynaksız), FUNC (`functionGroup`), seed sınıfını çalıştırma (`run: true`), test include. +- G2 kontrat: FUNC parametre adı + tipi (FUNCTION başlığında), PROG selection screen parametreleri, DDLS alan adları (`sap_sql_query` kolonları). +- Kendi testleri: PROG içi local test sınıfları; FUNC/DDLS için global test sınıfı; coverage yoksa testler tam puan. +- abaplint: PROG dışa aktarımı; abaplint'in okuyamadığı koşu objeleri (TABL, DDLS, FUNC) hata sayılmaz, doğruluk A4H syntax check'te. +- Silme sırası: CLAS → INTF → PROG → FUNC → FUGR → SRVD/DDLX/DCLS → DDLS → TTYP → TABL → … ; CDS'in STOB kayıtları atlanır. +- Kurulum başarısızsa koşu puanlanmaz (`setup_failed`), objeler silinir. + +**Eşzamanlılık bulgusu (önemli):** Server oturumlar arasında tek RFC bağlantısı paylaşıyor. Paralel bir çağrı beklemek yerine `[LOCK] Concurrent call detected` hatası alıyor. Koşu 214'te T14'ün seed tablosu bu yüzden aktive olmadı. Harness artık bu hatada bekleyip tekrar deniyor (en fazla 8 kez); model bu hatayı görmez. Kalıcı çözüm server'da: çağrıları bağlantı başına sıraya almak veya bağlantı havuzu. + +**Koşular:** + +| Görev | Tip | Oracle | Null | DeepSeek V4.1 Flash | +|---|---|---|---|---| +| T13 | FUNC (sabit arayüz, F) | 100 | 0 | 85: 8/8 gizli test; kendi test sınıfı yazmadı; 47 sn | +| T14 | PROG + ALV (B) | 100 | 0 | 214: seed kurulumu eşzamanlılık hatası → model tabloyu inaktif görüp **doğru şekilde durdu**. 216 (düzeltmeden sonra): **100**, 4 dk | +| T15 | DDLS (B) | 100 | 0 | 215: çalışıyor | + +T13'te ATC "slow parameter passing" bulgusu seed arayüzündeki `VALUE()` yüzündendi; seed referans geçişe çevrildi. + +## 11e. Pilot sonuçları (2026-10-02, G0002–G0021) + +DeepSeek V4.1 Flash, 20 görev, her görev en fazla 3 onarım turu. Sonuçlar: `runs/gen/pilot.json`. + +| | Sonuç | +|---|---| +| Kabul (oracle 100, null 0) | **16 / 20 (%80)**. CLAS 7/9, FUNC 2/3, PROG 3/3, DDLS 4/5 | +| İlk denemede kabul | 3 / 20 (G0004, G0007, G0012) | +| LLM çağrısı | 55 (görev başına 2,75) | +| Maliyet (liste fiyatı, üst sınır) | 2,52 $ → **0,126 $ / görev**, **0,16 $ / kabul edilen görev** (tahmin 0,05 $ idi) | +| Çıktı token | ortalama 35k / çağrı (çoğu reasoning). Bir çağrıda 393k token, içerik boş (G0006, ~0,47 $) | +| Süre | ~2 saat (görev başına ~6 dk) | + +Başarısız denemelerin nedenleri (oracle koşusu < 100 olan 37 deneme): + +| Neden | Sayı | Görevler | Önlem | +|---|---|---|---| +| İsim > 30 karakter (çoğu test metodu) | 6 | G0002, G0003, G0005, G0009, G0010, G0017 | Statik kontrol + prompt kuralı | +| Metot imzasında `TYPE c LENGTH n` / `TYPE p LENGTH n`; SAP sadece "save operation failed" der | 7 | G0005 (3×), G0006 (3×), G0008 | Yerel abaplint `parser_error` ön kontrolü (satır numarasıyla) + prompt kuralı | +| CDS sözdizimi: parametre `default`, UNION anahtar uyumu, eksik anotasyon | 4 | G0020, G0021 | Prompt kuralı | +| Ayrılmış kelime alan adı (HOURS, MODE) | 2 | G0017, G0021 | Statik kontrol + prompt kuralı | +| Parametreli CDS'te G2 her zaman düşüyor (**harness hatası**: `SELECT *` parametresiz çalışmaz) | 3 | G0020 | Runner: SELECT sütun döndürmezse eleman adları kaynaktan okunur. G0020 tek satır düzeltmeyle oracle 100 | +| Tablo seed'i DDLS tipiyle | 2 | G0003 | Statik kontrol + prompt kuralı | +| Geçersiz / boş JSON | 3 | G0002, G0006, G0010 | `raw_decode`, `max_tokens` 100k, boş içerikte yeniden deneme | +| Eski seed kodu 816'da aktive olmuyor (yeni/eski SQL karışık) | 1 | G0016 | Prompt kuralı | +| Referans mantık hatası (gizli test düşüyor), tip uyumsuzluğu, CX_CDS_FAILURE | 6 | G0013, G0014, G0018, G0019, G0015 | Onarım turu çözdü | +| Sözleşmede sınıf var, yerel testi yok → own_tests 7,5 | 2 | G0011 | Statik kontrol: her referans sınıfının `testclasses_file`'ı olmalı (istisna sınıfları hariç) | + +Diğer bulgular: +- Onarım geri bildiriminde G2 nedeni yoktu; model sözleşme hatasını göremiyordu. Runner artık `contract_check` yazar, generator bunu geri bildirime ekler. +- Kabul edilen G0013'ün sözleşmesinde test sınıfı var (`REPL_TEST`). Statik kontrol artık bunu reddeder. G0013 incelemede düzeltilmeli. +- Üretilen görevler örnekteki isimleri kopyalıyor (`T02_HIDDEN`, `T14_TEST`). Kozmetik. +- Statik kontrol ve abaplint ön kontrolü SAP'ye gitmez. Pilottaki 37 başarısız denemenin ~18'ini doğrulama koşusundan önce yakalar (tahmin). + +## 12. Açık sorular + +Cevaplananlar (Adım 1.2): +- ~~MCP yazma tool'ları~~ → bölüm 5. +- ~~A4H release ve abapGit~~ → SAP_BASIS 816 SP01. abapGit standalone kurulu (`ZABAPGIT_STANDALONE`). Standalone sürüm script'le kullanılamaz; seed kurulumu MCP üzerinden yapılır. code pal for ABAP kurulumu için kullanılabilir. + +- ~~Eşzamanlı oturum sınırı~~ → 8 (MCP server), bölüm 5. +- ~~abaplint ve seed tipleri~~ → çalışıyor, bölüm 6.5. + +Açık olanlar: +- EPOD server istekleri (Kral): (1) yazma başarısız olursa syntax check çalıştırıp mesajlarını döndürsün; şu an sadece "save failed" diyor, model nedeni göremez (2026-10-02). (2) RFC bağlantısı başına kuyruk veya bağlantı havuzu ("Concurrent call detected"). (3) BDEF oluşturma (RAP). +- Genel ABAP MCP arayüzünün spec'i (`abap-mcp-arayuz.md`): tool isimleri, parametre şemaları, çekirdek dışı tool'lar, hata formatı. Sonra yazılacak. +1. ~~Görevleri kim yazar?~~ → Öneri (2026-10-02): DeepSeek V4.1 Flash yazar. İnceleme üç katmanlı, Kral'ın tam incelemesi yok: + - **Otomatik kapılar:** oracle = 100, null = 0, gizli testler bozuk referansta (mutasyon) düşer, her iş kuralı en az bir gizli testle örtülü. + - **Ampirik süzgeç:** Görev 2–3 modelle koşulur. Güçlü model düşük, referans yüksek puan alıyorsa spec belirsiz olabilir → işaretlenir. Herkesin kolayca geçtiği görevler eval'den çıkarılır. + - **Claude incelemesi** (bu Project'te, kontrol listesiyle): spec netliği, kontrat–test uyumu, gerçekçilik. Sadece eval görevleri; eval görevleri eğitim verisine girmez. + - **Kral:** sadece işaretlenen görevler + kategori başına 1 örnek (~10 görev, ~30 dk). +2. Hakem modeli yerel mi, uzak mı? + +## 13. Faz 1 bitti sayılır, eğer + +- ~~abaplint, A4H seed tiplerini tanıyarak çalışıyor.~~ ✓ +- Harness T01–T10'u uçtan uca koşturuyor ve puanlıyor. +- 100 görevlik set yazılmış ve senin incelemenden geçmiş. +- En az iki baz model ve Claude referansı ölçülmüş. diff --git a/docs/yol-haritasi.md b/docs/yol-haritasi.md new file mode 100644 index 0000000..7555c8b --- /dev/null +++ b/docs/yol-haritasi.md @@ -0,0 +1,161 @@ +# ABAP Danışman Modeli — Yol Haritası + +Durum: v19 · 2026-10-02 +Şu anki konum: **Adım 1.3** + +## Adım 0 — Çerçeve ve kararlar + +| Karar | Durum | +|---|---| +| Modelin rolü: teknik ABAP danışmanı, modül bilgisi yok | ✓ | +| Sözleşme: metin olarak ABAP görevi + genel ABAP MCP arayüzü | ✓ (2026-10-02) | +| Girdi: `plan-agent.md` formatı en iyi sonuç veren girdi, zorunlu değil; Kral'ın akışında plan kullanımı değişmez | ✓ (2026-10-02) | +| Tool arayüzü: EPOD isimleri yerine genel ABAP MCP; EPOD native destekleyecek, harness proxy ilk adapter; delete/teardown harness'te | ✓ (2026-10-02) | +| Eğitimde az miktarda tool isim/şema varyasyonu | ✓ (2026-10-02) | +| Ortam: A4H (yazma + doğrulama) | ✓ | +| Mevcut planlar: sadece şablon, eğitim verisi değil | ✓ | +| Eski playbook formatı kullanılmaz | ✓ (2026-10-02) | +| Eğitim verisinde Claude çıktısı yok; eval görevleri eğitim verisine girmez | ✓ (2026-10-02) | +| Lisans: Apache 2.0 | ✓ | +| Baz model: sadece Apache 2.0 veya MIT lisanslı adaylar | ✓ (seçim Adım 2'de) | + +Bitti. ✓ + +## Adım 1 — Eval seti ve harness + +Amaç: modeli ölçebilmek. Ölçüm olmadan eğitim kör olur. + +| Alt adım | İş | Durum | +|---|---|---| +| 1.1 | Tasarım dokümanı (`faz1-tasarim.md`) | ✓ v3 | +| 1.2 | Teknik doğrulamalar: MCP tool'ları, A4H release, teardown (ADT deletion API), abaplint + seed tipleri, paralellik (8 oturum) | ✓ | +| 1.3 | Harness iskeleti: setup → run → puan → teardown, T01 ile uçtan uca | **✓** — Mac mini'de; test include ve teardown çözüldü. Baştan koşular: oracle 100, null 0, Qwen (koşu 203) çalışıyor | +| 1.3b | Genel ABAP MCP arayüz spec'i (`abap-mcp-arayuz.md`) ve harness proxy'sinin bu arayüze çevrilmesi | Sonra | +| 1.4 | T01–T15: seed, gizli testler, hatalı referans | | +| 1.5 | 110 görevlik set (kategori K dahil: eksik/serbest girdi + farklı tool şeması): üretim + senin incelemen | | +| 1.6 | Ölçüm: aday baz modeller + Claude referansı | | + +Bitti sayılır: 110 görev, kategori bazında sonuç tablosu. + +## Hacim tahmini (kaba, 2026-10-02) + +İlk ölçümlerle kalibre edilecek (koşu süresi, kabul oranı). + +| Aşama | Görev | Koşu | Obje (oluşturulup silinen) | Süre | +|---|---|---|---|---| +| Eval seti (Adım 1) | 110 | 110 / model / mod | ~5–8 / koşu → ~700 / ölçüm | Yerel 27B: 35–50 saat / ölçüm | +| Ölçüm turu (Adım 1.6) | 110 | ~3–4 model × 2 mod + Claude ≈ 900 | ~6 000 | Yerel modeller birkaç hafta; uzak modeller birkaç gün | +| Eğitim verisi (Adım 3) | 1 500–2 000 üretilmiş görev | 4 deneme / görev ≈ 6 000–8 000 | ~40 000–50 000 | Yerel 27B ile ~5 000 saat → **yerelde mümkün değil** | +| Hedef çıktı (Adım 3) | | %40 kabul → **~3 000 yörünge** | | | +| RL (Adım 5, opsiyonel) | aynı havuz | on binlerce | yüz binler | Ayrı planlama | + +**Teacher maliyeti (DeepSeek V4.1 Flash, Ollama cloud):** T01'de ölçülen ≈ 0,09 $ / koşu (liste fiyatı). T01 kolay bir görev; ortalama görev için 0,15–0,20 $ varsayımı: + +| İş | Koşu | Maliyet (liste fiyatı) | +|---|---|---| +| Eval ölçümü, DeepSeek (110 × 2 mod) | 220 | ~35–45 $ | +| Adım 3 (2 000 görev × 2 deneme; T01'deki başarıyla kabul oranı yüksek beklenir) | ~4 000 | ~600–800 $ | +| **Toplam (RL hariç)** | | **~650–850 $** | + +Ollama paneli, koşu 204'ün 8 isteği tek tek: 6 × <0,01 $, 1 × 0,01 $, 1 × 0,03 $ → **≈ 0,05–0,08 $ / koşu**. Liste fiyatı tahminine (0,09 $) yakın; Ollama kullanımı liste fiyatına yakın sayıyor. (Ayın toplamındaki düşük ortalama, başka küçük isteklerden geliyor.) + +| İş | Koşu / görev | Tahmin | +|---|---|---| +| Pilot (B–E): ~20 görev üretimi + doğrulama + koşular | ~100 istek grubu | ~5–10 $ | +| Eval seti üretimi + ölçüm (110 görev, 2 mod) | ~330 | ~25–40 $ | +| Eğitim görevi üretimi (2 000 görev) | 2 000 | ~100 $ | +| Yörünge üretimi (2 000 × 2 deneme) | ~4 000 | ~400–600 $ | +| **Toplam (RL hariç)** | | **~550–750 $** | + +Bütçe: şu an ~50 $, her ay +60 $. B–F ve eval ölçümü bu bütçeye sığar. Yörünge üretimi aylık 60 $ ile 7–10 ay sürer; o aşamada ~500–700 $ ek kredi önerilir. Kesin rakam pilottan sonra. + +**Karar (2026-10-02): DeepSeek V4.1 Flash (Ollama cloud) teacher ve görev yazarı olarak hemen kullanılıyor.** + +Sonuçlar: +- Aynı anda A4H'da en fazla ~50 obje olur (6 worker × ~8). Toplam sayı büyük ama birikmez; teardown her koşu sonunda. +- **Adım 3 yerel modelle yapılamaz.** Teacher model hızlı ve uzakta olmalı (kiralık GPU veya açık lisanslı modelin API'si). O zaman darboğaz A4H olur: 6 worker, koşu başına ~5 dk → saatte ~70 koşu → 8 000 koşu ≈ 5 gün. +- **Önek şeması yetmez:** `Z_` en fazla 999 koşu. Adım 3 öncesi koşu kimliği base36 4 karaktere çıkarılmalı (~1,7 milyon koşu). +- Uzun vadede A4H'da izlenecekler: disk (HANA log, versiyon geçmişi, silme logları). + +## Hızlandırılmış sıra (2026-10-02) + +Adım 3 görev ister; görevler el ile yazılırsa darboğaz olur. Bu yüzden ilk iş **görev üreteci**: hem eval setini (1.5) hem eğitim görevlerini (3.1) aynı hat üretir. + +| Sıra | İş | Durum | +|---|---|---| +| A | Önek şeması: koşu 4 karakter base36, görev 3 karakter base36 (`Z005P001_`) | ✓ | +| B | Harness: FUGR/FUNC, PROG, DDLS desteği (G2 kontrat kontrolü, abaplint dışa aktarımı, silme sırası) | ✓ T13–T15 oracle 100 / null 0. Server eşzamanlılık sorunu bulundu (harness'te retry) | +| C | Görev üreteci: DeepSeek görev yazar (spec, seed, gizli testler, referans + local testler); kategori ve obje tipi dağılımına göre | ✓ `harness/generator.py`: üretim → yapı + statik kontrol + yerel abaplint → oracle/null doğrulama → en fazla 3 onarım turu. Pilot sonrası iyileştirildi (faz1-tasarim 11e) | +| D | Otomatik görev doğrulama: oracle = 100, null = 0, gizli testler bozuk referansta düşmeli | oracle/null hazır; mutasyon kontrolü sonra | +| E | Pilot: ~20 görev, tüm obje tipleri; kabul oranı ve gerçek maliyet ölçümü | ✓ 16/20 kabul, 0,16 $ / kabul edilen görev (faz1-tasarim 11e). İyileştirilmiş generator henüz denenmedi | +| F | Eval seti: 110 görev; Kral incelemesi. Eğitim havuzu: otomatik doğrulanmış görevler, eval ile çakışmasız | | +| G | Ölçüm (1.6) ve yörünge üretimi (3.2) paralel worker'larla (bulut model: en fazla 6) | | + +## Bütçe takvimi (Ollama cloud, 2026-10-02) + +Kalan: ~50 $ (12 Ekim'e kadar). 12 Ekim'de +60 $, sonra her ay +60 $. Birim maliyet (ölçülen): DeepSeek koşusu ≈ 0,05–0,08 $; görev üretimi ≈ 0,13 $ / görev, 0,16 $ / kabul edilen görev (pilot, liste fiyatı). Yerel modeller (Qwen vb.) ücretsiz; gece koşuları. + +| Dönem | Bütçe | İş | Tahmini harcama | +|---|---|---|---| +| **2–12 Ekim** | ~50 $ | C: görev üreteci. D: otomatik doğrulama. E: pilot (20 görev, üretim + DeepSeek koşusu) | ~5 $ | +| | | F: eval seti — ~150 aday görev üret (~20 $), süz, 110 kalsın; her aday 1–2 DeepSeek koşusu (ampirik süzgeç, ~10–20 $) | ~30–40 $ | +| | | Yerel ölçüm (Qwen ve aday baz modeller, ücretsiz, geceleri) | 0 $ | +| | | Yedek | ~15 $ | +| **12 Ekim – 12 Kasım** | 60 $ | Adım 2: baz model seçimi (yerel ölçüm sonuçlarıyla). Adım 3 pilotu: ~300 eğitim görevi + ~600 yörünge denemesi | ~60 $ | +| | | Pilot sonucu: gerçek kabul oranı ve maliyet → Adım 3'ün tamamı için ek kredi kararı (tahmin ~400–600 $) | | +| **12 Kasım sonrası** | 60 $/ay + ek kredi | Adım 3'ün tamamı, ardından Adım 4 (SFT) | | + +Kurallar: +- Harness her bulut isteğinin token kullanımını `runs/ledger.jsonl`'e yazar (liste fiyatı, cache indirimi olmadan = üst sınır). Dönem harcaması `BUDGET_LIMIT_USD`'ye (şu an 45 $) ulaşınca yeni üretim veya bulut koşusu başlamaz. ✓ +- Eval görevleri ve eğitim görevleri ayrı havuzlar; eval görevleri eğitim verisine girmez. +- Bulut ve yerel model aynı Ollama sunucusunda kuyrukta bekleyebilir; ölçümler sırasında ikisi aynı anda koşturulmaz. + +## Adım 2 — Baz model seçimi + +Adım 1.6 tablosundan seçilir. Aday havuzu: sadece Apache 2.0 veya MIT lisanslı modeller. Kriterler: puan, boyut, Mac mini'de çalışabilirlik. + +Bitti sayılır: tek baz model seçildi, gerekçesi yazıldı. + +## Adım 3 — Eğitim verisi üretimi + +| Alt adım | İş | +|---|---| +| 3.1 | Görev üreteci: eval görevleriyle aynı şemada binlerce sentetik görev. Eval görevleriyle çakışma yok. Girdilerin bir kısmı serbest metin; yörüngelerin az bir kısmı farklı tool isim/şemalarıyla. | +| 3.2 | Yörünge üretimi: açık lisanslı teacher model görevleri A4H'da çözer. | +| 3.3 | Filtre: sadece puan eşiğini geçen yörüngeler kalır. Deduplikasyon. | +| 3.4 | Opsiyonel: izinli lisanslı açık ABAP kodu (continued pretraining için). | + +Eğitim verisinde mutlaka olmalı (pilot bulguları, 2026-10-02): modellerin sık yaptığı hatalar için görev ve onarım yörüngeleri. +- Metot parametresinde `TYPE c LENGTH n` geçersiz; adlandırılmış tip (`TYPES`) gerekir. +- Ayrılmış kelime alan adı (HOURS, MODE). +- 30 karakterden uzun isimler (özellikle test metotları). + +Bitti sayılır: filtrelenmiş yörünge seti hazır, kategori dağılımı dengeli. + +## Adım 4 — SFT (LoRA) + +| Alt adım | İş | +|---|---| +| 4.1 | Küçük deneme: Mac mini, MLX | +| 4.2 | Gerçek koşu: kiralık GPU | +| 4.3 | Eval: Adım 1 setiyle, baz modelle karşılaştırma | + +Bitti sayılır: eğitilmiş model baz modeli anlamlı farkla geçiyor. + +## Adım 5 — RL (opsiyonel) + +SFT hedefe ulaşmazsa. Ödül: harness puanı (syntax, aktivasyon, testler, abaplint, ATC). + +Bitti sayılır: hedef çizgi (Claude referansına ≤ 10 puan fark). + +## Adım 6 — Gerçek iş akışında deneme + +`/sapplan` planı → model build-agent rolünde → gerçek bir görev. Eval'in yakalamadığı sorunlar burada çıkar. + +Bitti sayılır: senin seçtiğin gerçek görevlerde kabul edilebilir sonuç. + +## Adım 7 — Yayın + +Ağırlıklar, model kartı, lisans, eval sonuçları, genel ABAP MCP arayüz spec'i. Opsiyonel: harness ve eval seti açık kaynak. + +Bitti sayılır: yayınlandı. diff --git a/harness/generator.py b/harness/generator.py index df0e59f..38e07c1 100644 --- a/harness/generator.py +++ b/harness/generator.py @@ -6,6 +6,9 @@ import argparse import json import os import re +import shutil +import subprocess +import tempfile import time import urllib.request @@ -15,6 +18,9 @@ from .ledger import add_usage, check_budget from .runner import Runner ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +ABAPLINT = os.path.join(ROOT, "node_modules", ".bin", "abaplint") +MAX_OUT_TOKENS = 100000 +ABAPLINT_VERSIONS = {"v702", "v740sp05", "v750", "v758"} EXAMPLE_FOR = {"CLAS": "T01", "INTF": "T01", "FUNC": "T13", "PROG": "T14", "DDLS": "T15", "TABL": "T15"} CATEGORIES = { "A": "pure logic in a new class (language, OO design, Clean ABAP)", @@ -37,6 +43,18 @@ Rules for the bundle: The harness replaces it with a run prefix of 9 characters (for example Z005P001_). Name length after replacement: classes, programs, function modules, CDS entities max 30; database tables max 16; function groups max 26. +- Every other ABAP name (methods, test methods, local classes, data, types, constants) has max 30 + characters. Keep test method names short, for example "rejects_zero_qty". +- Method parameters need a complete type: "TYPE c LENGTH 4" is not allowed in a signature. + Declare a type first (TYPES ty_zone TYPE c LENGTH 4) and use it. +- Field names in tables and CDS views are not SQL reserved words (no HOURS, MODE, ORDER, DATE, COUNT ...). +- CDS: use "define view entity". Parameters have no default value. In a UNION, all branches have the same + key elements and element names. Hidden tests for a view with parameters pass all parameters. +- Legacy seed code (categories E, I) must activate on SAP_BASIS 816: do not mix old and new syntax in one + SQL statement (with new syntax, every host variable needs "@"). +- The contract lists only the objects that the hidden tests call. Test classes are never in the contract. + Every reference class (except exception classes) has a "testclasses_file". +- A database table seed has type "TABL" and DDL source "define table ...". Type "DDLS" is only for CDS views. - All objects are in package $TMP. Do not use transports. - Do not use SAP application module data (no FI, SD, MM tables). Use generic business domains and only objects that exist in every ABAP Platform system (language, ABAP SQL, CDS, CL_ABAP_*, CL_SALV_TABLE, @@ -84,7 +102,8 @@ def bundle_of(task_dir): def chat(model, messages, base_url): - body = {"model": model, "messages": messages, "temperature": 0.7} + # max_tokens: one pilot call ran to 393k output tokens (reasoning) and returned no content + body = {"model": model, "messages": messages, "temperature": 0.7, "max_tokens": MAX_OUT_TOKENS} req = urllib.request.Request(f"{base_url}/chat/completions", json.dumps(body).encode(), {"Content-Type": "application/json", "Authorization": "Bearer none"}) for attempt in range(4): @@ -92,7 +111,13 @@ def chat(model, messages, base_url): with urllib.request.urlopen(req, timeout=1800) as r: data = json.loads(r.read().decode()) add_usage(model, data.get("usage", {}), kind="generate") - return data["choices"][0]["message"].get("content") or "" + content = data["choices"][0]["message"].get("content") or "" + if content.strip(): + return content + # empty content (output limit reached in reasoning): retry once more with the next attempt + if attempt == 3: + return "" + continue except Exception as e: # noqa: BLE001 if getattr(e, "code", 500) < 500 and getattr(e, "code", 500) != 429: raise @@ -103,8 +128,109 @@ def chat(model, messages, base_url): def parse_bundle(text): text = text.strip() text = re.sub(r"^```(json)?\s*|\s*```$", "", text) - start, end = text.find("{"), text.rfind("}") - return json.loads(text[start:end + 1]) + # raw_decode stops after the first complete object; text after it (a note, a second object) is ignored + return json.JSONDecoder().raw_decode(text[text.find("{"):])[0] + + +NAME_RE = re.compile(r"^\s*(?:CLASS-)?(?:METHODS|METHOD|DATA|TYPES|CONSTANTS|FORM|CLASS|INTERFACE)\b:?\s+(\w+)", + re.I | re.M) +# reserved words seen in activation errors, plus common SQL keywords +RESERVED = {"HOURS", "MODE", "ORDER", "GROUP", "DATE", "TIME", "VALUE", "USER", "KEY", "COUNT", "SUM", "MIN", + "MAX", "AVG", "SELECT", "FROM", "WHERE", "TABLE", "VIEW", "UNION", "JOIN", "LEVEL", "SIZE", "TYPE", + "INDEX", "CASE", "WHEN", "THEN", "ELSE", "END", "AS", "ON", "BY", "DESC", "ASC", "DAYS", "MINUTES", + "SECONDS", "YEAR", "MONTH", "DAY", "LIMIT", "OFFSET", "PARAMETERS"} +GLOBAL_TEST_CLASS = re.compile(r"\A\s*(?:\*[^\n]*\n|\"[^\n]*\n|\s)*CLASS\s+\S+\s+DEFINITION[^.]*FOR\s+TESTING", + re.I) +DDL_KIND = re.compile(r"^\s*define\s+(?:root\s+)?(table|view|structure)\b", re.I | re.M) + + +def lint_files(b): + """Static checks that need no SAP system: identifier length, seed type against DDL source.""" + errs = [] + for rel, src in b.get("files", {}).items(): + if not rel.endswith(".abap"): + continue + for name in set(NAME_RE.findall(src.replace("{{P}}", "Z0000000_").replace("{{p}}", "z0000000_"))): + if len(name) > 30: + errs.append(f"{rel}: name {name} has {len(name)} characters (ABAP max 30)") + t = b.get("task", {}) + oos = {n.upper() for n in t.get("out_of_scope", [])} + test_names = {o.get("name", "").upper() for o in t.get("hidden_tests", [])} + for o in t.get("reference", []): + name = o.get("name", "") + if name.upper() in oos: + errs.append(f"reference object {name} is also in out_of_scope; the solution must change it") + if o.get("type") == "CLAS" and GLOBAL_TEST_CLASS.search(b.get("files", {}).get(o.get("file", ""), "")): + test_names.add(name.upper()) + for c in t.get("contract", []): + if c.get("name", "").upper() in test_names: + errs.append(f"contract contains the test class {c.get('name')}; only objects that the hidden " + "tests call belong in the contract") + for o in t.get("reference", []): + if (o.get("type") == "CLAS" and o.get("name", "").upper() not in test_names + and not o.get("testclasses_file") + and not re.search(r"INHERITING\s+FROM\s+\S*CX_", b.get("files", {}).get(o.get("file", ""), ""), + re.I)): + errs.append(f"reference class {o.get('name')} has no testclasses_file (local unit tests)") + for rel, src in b.get("files", {}).items(): + if DDL_KIND.search(src): + for f in re.findall(r"^\s*(?:key\s+)?(\w+)\s*:", src, re.I | re.M): + if f.upper() in RESERVED: + errs.append(f"{rel}: field name {f} is a reserved word; choose another name") + for k in ("seed", "reference"): + for o in t.get(k, []): + m = DDL_KIND.search(b.get("files", {}).get(o.get("file", ""), "")) + if not m: + continue + want = {"table": "TABL", "structure": "TABL", "view": "DDLS"}[m.group(1).lower()] + if o.get("type") != want: + errs.append(f"{k}: {o.get('name')} has type {o.get('type')}, but its source is " + f"'define {m.group(1)}'; use type {want}") + return errs + + +def abaplint_files(b): + """Parser errors of ABAP sources (classes, interfaces, programs) found by local abaplint. + SAP often reports only "save operation failed" for these; abaplint gives the line. + Only parser_error: check_syntax gives false errors for standard objects abaplint does not know.""" + t, files = b.get("task", {}), b.get("files", {}) + ext = {"CLAS": "clas", "INTF": "intf", "PROG": "prog"} + version = t.get("release_target") if t.get("release_target") in ABAPLINT_VERSIONS else "v758" + d = tempfile.mkdtemp(prefix="genlint_") + origin = {} + try: + os.makedirs(os.path.join(d, "src")) + for k in ("seed", "reference", "hidden_tests"): + for o in t.get(k, []): + if o.get("type") not in ext: + continue + name = re.sub(r"\W", "_", o.get("name", "").replace("{{P}}", "Z0000000_")).lower() + for fk, suffix in (("file", ""), ("testclasses_file", ".testclasses")): + if o.get(fk) in files: + fn = f"{name}.{ext[o['type']]}{suffix}.abap" + origin[fn] = o[fk] + src = files[o[fk]].replace("{{P}}", "Z0000000_").replace("{{p}}", "z0000000_") + open(os.path.join(d, "src", fn), "w").write(src) + if not origin: + return [] + cfg = {"global": {"files": "/src/**/*.*"}, "dependencies": [], + "syntax": {"version": version, "errorNamespace": "^(Z|Y)"}, "rules": {"parser_error": True}} + json.dump(cfg, open(os.path.join(d, "abaplint.json"), "w")) + p = subprocess.run([ABAPLINT, "abaplint.json", "-f", "json"], cwd=d, capture_output=True, text=True, + timeout=300) + issues = json.loads(p.stdout or "[]") + except Exception: # noqa: BLE001 the pre-check is optional; SAP validation follows + return [] + finally: + shutil.rmtree(d, ignore_errors=True) + out = [] + for i in issues: + f = i.get("file", "") + fn = os.path.basename(f.get("filename", "") if isinstance(f, dict) else str(f)) + if i.get("key") == "parser_error" and fn in origin: + out.append(f"{origin[fn]} line {i.get('start', {}).get('row')}: syntax error for release {version} " + f"(abaplint): {i.get('description')}") + return out[:20] def check_bundle(b): @@ -165,7 +291,8 @@ def write_errors(run_dir): def failure_summary(rep): - out = {"reference_write_errors": write_errors(rep.get("_dir")),"gates": rep.get("gates"), "score": rep.get("score"), "setup": rep.get("setup"), + out = {"reference_write_errors": write_errors(rep.get("_dir")), "gates": rep.get("gates"), + "contract_check": rep.get("contract_check"), "score": rep.get("score"), "setup": rep.get("setup"), "atc": rep.get("atc"), "abaplint": rep.get("abaplint"), "own_tests": rep.get("own_tests"), "hidden_failed": [d for d in rep.get("hidden_tests", {}).get("detail", []) if not d["ok"]], "hidden_install": rep.get("hidden_tests", {}).get("install")} @@ -189,11 +316,14 @@ def generate(task_id, pool, object_type, category, difficulty, model, base_url, messages.append({"role": "assistant", "content": text}) try: b = parse_bundle(text) - errs = check_bundle(b) + found = {"structure": check_bundle(b), "static": lint_files(b), "abaplint": abaplint_files(b)} except Exception as e: # noqa: BLE001 - b, errs = None, [f"invalid JSON: {e}"] + b, found = None, {"json": [f"invalid JSON: {e}"]} + errs = [e for v in found.values() for e in v] if errs: - log["attempts"].append({"stage": "bundle", "errors": errs}) + # one entry per source, so a batch shows what the checks found before SAP + log["attempts"].append({"stage": "bundle", "errors": errs, + "by_check": {k: len(v) for k, v in found.items() if v}}) messages.append({"role": "user", "content": "Fix these problems and return the full bundle again:\n" + "\n".join(errs)}) continue diff --git a/harness/pilot.py b/harness/pilot.py index 5904dc3..883fb08 100644 --- a/harness/pilot.py +++ b/harness/pilot.py @@ -14,25 +14,33 @@ PLAN = [ ("PROG", "B"), ("PROG", "B"), ("PROG", "E"), ("DDLS", "B"), ("DDLS", "B"), ("DDLS", "B"), ("DDLS", "B"), ("DDLS", "B"), ] +# hard batch after the pilot: the types that failed in the pilot (2026-10-03) +HARD = [("CLAS", "D", None), ("CLAS", "E", "legacy price or freight calculation with packed numbers"), + ("DDLS", "B", "view with input parameters"), ("DDLS", "B", "UNION of two tables with a literal source element"), + ("FUNC", "A", None)] DDLS_TOPICS = ["join of two tables and a calculated element", "association and path expression", "aggregation with CASE and GROUP BY", "view with input parameters", "UNION of two tables with a literal source element"] def main(): + """python3 -m harness.pilot FIRST_ID [hard RUN_BASE]""" load_env(os.path.join(ROOT, ".env")) first = int(sys.argv[1]) if len(sys.argv) > 1 else 2 + hard = len(sys.argv) > 2 and sys.argv[2] == "hard" + run_base = int(sys.argv[3]) if len(sys.argv) > 3 else 1100 + name = "hard" if hard else "pilot" + plan = HARD if hard else [(o, c, None) for o, c in PLAN] results = [] d = 0 - for k, (otype, cat) in enumerate(PLAN): + for k, (otype, cat, topic) in enumerate(plan): tid = f"G{first + k:04d}" - topic = None - if otype == "DDLS": + if otype == "DDLS" and not hard: topic, d = DDLS_TOPICS[d], d + 1 try: log = generate(tid, "eval", otype, cat, 2, "deepseek-v4.1-flash:cloud", os.environ.get("LLM_BASE_URL", "http://127.0.0.1:11434/v1"), - 1100 + 10 * k, topic) + run_base + 10 * k, topic) except BudgetExceeded as e: print("BUDGET", e, flush=True) break @@ -41,7 +49,7 @@ def main(): log["spent_total"] = spent() results.append(log) print(json.dumps(log), flush=True) - json.dump(results, open(os.path.join(ROOT, "runs", "gen", "pilot.json"), "w"), indent=1) + json.dump(results, open(os.path.join(ROOT, "runs", "gen", f"{name}.json"), "w"), indent=1) if __name__ == "__main__": diff --git a/harness/runner.py b/harness/runner.py index cd89c61..b9e0fe6 100644 --- a/harness/runner.py +++ b/harness/runner.py @@ -23,6 +23,22 @@ DELETE_ORDER = ["CLAS", "INTF", "PROG", "FUNC", "FUGR", "SRVD", "DDLX", "DCLS", SOURCE_TYPES = ("CLAS", "INTF", "PROG", "FUNC", "DDLS", "DCLS", "DDLX", "TABL") +def _cds_elements(src): + """Element names of a CDS view select list (alias, else last path component). Used when a + SELECT on the view is not possible, for example a view with parameters.""" + body = re.sub(r"//[^\n]*|/\*.*?\*/", "", src or "", flags=re.S) + m = re.search(r"\{(.*)\}", body, re.S) + out = set() + for el in re.split(r",(?![^()]*\))", m.group(1) if m else ""): + el = re.sub(r"@\S+(\s*:\s*('[^']*'|\S+))?", "", el).strip() + if not el or el.lower().startswith("_") and " as " not in el.lower(): + continue + alias = re.search(r"\bas\s+(\w+)\s*$", el, re.I) + name = alias.group(1) if alias else re.split(r"[.\s]", el.split()[-1])[-1] + out.add(name.upper()) + return out + + def _obj_args(otype, name, fg=None): a = {"objectType": otype, "objectName": name} if fg: @@ -222,26 +238,36 @@ class Runner: g1.append({"name": c["name"], "ok": ok}) g["G1_active"] = all(x["ok"] for x in g1) and bool(g1) g2 = True + g2_detail = [] for c in contract: src = sources.get((c["type"], c["name"].upper()), "") if c.get("implements") and not re.search(rf"INTERFACES\s+{re.escape(c['implements'])}\b", src, re.I): g2 = False + g2_detail.append(f"{c['name']}: does not implement {c['implements']}") if c["type"] == "FUNC": # signature: every parameter with its type in the FUNCTION header header = src.split(".", 1)[0] for prm in c.get("params", []): if not re.search(rf"\b{re.escape(prm['name'])}\b\)?\s+TYPE\s+{re.escape(prm['type'])}\b", header, re.I): g2 = False + g2_detail.append(f"{c['name']}: no parameter {prm['name']} TYPE {prm['type']} " + "in the FUNCTION header") if c["type"] == "PROG": # selection screen parameters for prm in c.get("parameters", []): if not re.search(rf"(PARAMETERS|SELECT-OPTIONS)\s*:?[^.]*\b{re.escape(prm)}\b", src, re.I): g2 = False + g2_detail.append(f"{c['name']}: no selection screen parameter {prm}") if c["type"] == "DDLS" and c.get("fields") and g["G1_active"]: _, q = mcp.call("sap_sql_query", {"query": f"SELECT * FROM {c['name']}", "maxRows": 1}) cols = {col.get("name", "").upper() for col in (_json(q) or {}).get("columns", [])} - if not {f.upper() for f in c["fields"]} <= cols: + if not cols: # view with parameters: SELECT without parameters fails + cols = _cds_elements(src) + missing = {f.upper() for f in c["fields"]} - cols + if missing: g2 = False + g2_detail.append(f"{c['name']}: CDS elements missing: {sorted(missing)}") g["G2_contract"] = g2 and g["G1_active"] + rep["contract_check"] = {"g1": g1, "g2": g2_detail} g["G4_out_of_scope"] = all( _norm(self._source(mcp, o["type"], o["name"], o.get("functionGroup"))) == _norm(seed_src[o["name"].upper()]) diff --git a/tasks_gen/eval/G0003/generation.json b/tasks_gen/eval/G0003/generation.json new file mode 100644 index 0000000..7dccc8a --- /dev/null +++ b/tasks_gen/eval/G0003/generation.json @@ -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 +} \ No newline at end of file diff --git a/tasks_gen/eval/G0003/hidden/t02_hidden.clas.abap b/tasks_gen/eval/G0003/hidden/t02_hidden.clas.abap new file mode 100644 index 0000000..1ee74fc --- /dev/null +++ b/tasks_gen/eval/G0003/hidden/t02_hidden.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0003/reference/course_booking.clas.abap b/tasks_gen/eval/G0003/reference/course_booking.clas.abap new file mode 100644 index 0000000..55d8f93 --- /dev/null +++ b/tasks_gen/eval/G0003/reference/course_booking.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0003/reference/course_booking.testclasses.abap b/tasks_gen/eval/G0003/reference/course_booking.testclasses.abap new file mode 100644 index 0000000..1f754e1 --- /dev/null +++ b/tasks_gen/eval/G0003/reference/course_booking.testclasses.abap @@ -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. diff --git a/tasks_gen/eval/G0003/seed/course.ddls.abap b/tasks_gen/eval/G0003/seed/course.ddls.abap new file mode 100644 index 0000000..9233ce5 --- /dev/null +++ b/tasks_gen/eval/G0003/seed/course.ddls.abap @@ -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; +} diff --git a/tasks_gen/eval/G0003/seed/course.ddls.asddls b/tasks_gen/eval/G0003/seed/course.ddls.asddls new file mode 100644 index 0000000..e32834e --- /dev/null +++ b/tasks_gen/eval/G0003/seed/course.ddls.asddls @@ -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; +} diff --git a/tasks_gen/eval/G0003/seed/course.tabl.asddls b/tasks_gen/eval/G0003/seed/course.tabl.asddls new file mode 100644 index 0000000..a7fd505 --- /dev/null +++ b/tasks_gen/eval/G0003/seed/course.tabl.asddls @@ -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; +} diff --git a/tasks_gen/eval/G0003/seed/seed_courses.clas.abap b/tasks_gen/eval/G0003/seed/seed_courses.clas.abap new file mode 100644 index 0000000..8ed1223 --- /dev/null +++ b/tasks_gen/eval/G0003/seed/seed_courses.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0003/spec.md b/tasks_gen/eval/G0003/spec.md new file mode 100644 index 0000000..e859ae0 --- /dev/null +++ b/tasks_gen/eval/G0003/spec.md @@ -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. diff --git a/tasks_gen/eval/G0003/task.json b/tasks_gen/eval/G0003/task.json new file mode 100644 index 0000000..4da9231 --- /dev/null +++ b/tasks_gen/eval/G0003/task.json @@ -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" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0004/generation.json b/tasks_gen/eval/G0004/generation.json new file mode 100644 index 0000000..5c7adc1 --- /dev/null +++ b/tasks_gen/eval/G0004/generation.json @@ -0,0 +1,14 @@ +{ + "id": "G0004", + "pool": "eval", + "object_type": "CLAS", + "category": "C", + "attempts": [ + { + "stage": "validate", + "oracle": 100.0, + "null": 0 + } + ], + "accepted": true +} \ No newline at end of file diff --git a/tasks_gen/eval/G0004/hidden/stock_age_hidden.clas.abap b/tasks_gen/eval/G0004/hidden/stock_age_hidden.clas.abap new file mode 100644 index 0000000..f072349 --- /dev/null +++ b/tasks_gen/eval/G0004/hidden/stock_age_hidden.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0004/reference/stock_age.clas.abap b/tasks_gen/eval/G0004/reference/stock_age.clas.abap new file mode 100644 index 0000000..5752e23 --- /dev/null +++ b/tasks_gen/eval/G0004/reference/stock_age.clas.abap @@ -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(). + 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 . + ENDIF. + + -quantity = -quantity + ls_batch-quantity. + -batch_count = -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(). + IF sy-subrc <> 0. + INSERT VALUE #( material = ls_batch-material ) INTO TABLE lt_group. + ASSIGN lt_group[ material = ls_batch-material ] TO . + ENDIF. + + -quantity = -quantity + ls_batch-quantity. + -batch_count = -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. diff --git a/tasks_gen/eval/G0004/reference/stock_age.testclasses.abap b/tasks_gen/eval/G0004/reference/stock_age.testclasses.abap new file mode 100644 index 0000000..9fbfd0e --- /dev/null +++ b/tasks_gen/eval/G0004/reference/stock_age.testclasses.abap @@ -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. diff --git a/tasks_gen/eval/G0004/seed/if_stock_age.intf.abap b/tasks_gen/eval/G0004/seed/if_stock_age.intf.abap new file mode 100644 index 0000000..998b066 --- /dev/null +++ b/tasks_gen/eval/G0004/seed/if_stock_age.intf.abap @@ -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. diff --git a/tasks_gen/eval/G0004/spec.md b/tasks_gen/eval/G0004/spec.md new file mode 100644 index 0000000..c7ee250 --- /dev/null +++ b/tasks_gen/eval/G0004/spec.md @@ -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. diff --git a/tasks_gen/eval/G0004/task.json b/tasks_gen/eval/G0004/task.json new file mode 100644 index 0000000..883e855 --- /dev/null +++ b/tasks_gen/eval/G0004/task.json @@ -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" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0005/generation.json b/tasks_gen/eval/G0005/generation.json new file mode 100644 index 0000000..12428e1 --- /dev/null +++ b/tasks_gen/eval/G0005/generation.json @@ -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 +} \ No newline at end of file diff --git a/tasks_gen/eval/G0005/hidden/t02_hidden.clas.abap b/tasks_gen/eval/G0005/hidden/t02_hidden.clas.abap new file mode 100644 index 0000000..979cfa9 --- /dev/null +++ b/tasks_gen/eval/G0005/hidden/t02_hidden.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0005/reference/cx_freight_error.clas.abap b/tasks_gen/eval/G0005/reference/cx_freight_error.clas.abap new file mode 100644 index 0000000..9e37a87 --- /dev/null +++ b/tasks_gen/eval/G0005/reference/cx_freight_error.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0005/reference/cx_invalid_weight.clas.abap b/tasks_gen/eval/G0005/reference/cx_invalid_weight.clas.abap new file mode 100644 index 0000000..8d7085a --- /dev/null +++ b/tasks_gen/eval/G0005/reference/cx_invalid_weight.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0005/reference/cx_unknown_zone.clas.abap b/tasks_gen/eval/G0005/reference/cx_unknown_zone.clas.abap new file mode 100644 index 0000000..b5bad26 --- /dev/null +++ b/tasks_gen/eval/G0005/reference/cx_unknown_zone.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0005/reference/cx_weight_limit.clas.abap b/tasks_gen/eval/G0005/reference/cx_weight_limit.clas.abap new file mode 100644 index 0000000..00ed07d --- /dev/null +++ b/tasks_gen/eval/G0005/reference/cx_weight_limit.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0005/reference/freight_calc.clas.abap b/tasks_gen/eval/G0005/reference/freight_calc.clas.abap new file mode 100644 index 0000000..8ed83fb --- /dev/null +++ b/tasks_gen/eval/G0005/reference/freight_calc.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0005/reference/freight_calc.testclasses.abap b/tasks_gen/eval/G0005/reference/freight_calc.testclasses.abap new file mode 100644 index 0000000..14e7bae --- /dev/null +++ b/tasks_gen/eval/G0005/reference/freight_calc.testclasses.abap @@ -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. diff --git a/tasks_gen/eval/G0005/seed/seed_data.clas.abap b/tasks_gen/eval/G0005/seed/seed_data.clas.abap new file mode 100644 index 0000000..7439444 --- /dev/null +++ b/tasks_gen/eval/G0005/seed/seed_data.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0005/seed/zone_master.clas.abap b/tasks_gen/eval/G0005/seed/zone_master.clas.abap new file mode 100644 index 0000000..954621d --- /dev/null +++ b/tasks_gen/eval/G0005/seed/zone_master.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0005/seed/zones.tabl.abap b/tasks_gen/eval/G0005/seed/zones.tabl.abap new file mode 100644 index 0000000..af9c132 --- /dev/null +++ b/tasks_gen/eval/G0005/seed/zones.tabl.abap @@ -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; +} diff --git a/tasks_gen/eval/G0005/spec.md b/tasks_gen/eval/G0005/spec.md new file mode 100644 index 0000000..ebcd945 --- /dev/null +++ b/tasks_gen/eval/G0005/spec.md @@ -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. diff --git a/tasks_gen/eval/G0005/task.json b/tasks_gen/eval/G0005/task.json new file mode 100644 index 0000000..0e7b5af --- /dev/null +++ b/tasks_gen/eval/G0005/task.json @@ -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" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0006/generation.json b/tasks_gen/eval/G0006/generation.json new file mode 100644 index 0000000..fdfb710 --- /dev/null +++ b/tasks_gen/eval/G0006/generation.json @@ -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 +} \ No newline at end of file diff --git a/tasks_gen/eval/G0006/hidden/t02_hidden.clas.abap b/tasks_gen/eval/G0006/hidden/t02_hidden.clas.abap new file mode 100644 index 0000000..0bfe660 --- /dev/null +++ b/tasks_gen/eval/G0006/hidden/t02_hidden.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0006/reference/freight_calculator.clas.abap b/tasks_gen/eval/G0006/reference/freight_calculator.clas.abap new file mode 100644 index 0000000..7d8faa2 --- /dev/null +++ b/tasks_gen/eval/G0006/reference/freight_calculator.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0006/reference/freight_calculator.testclasses.abap b/tasks_gen/eval/G0006/reference/freight_calculator.testclasses.abap new file mode 100644 index 0000000..57f81dc --- /dev/null +++ b/tasks_gen/eval/G0006/reference/freight_calculator.testclasses.abap @@ -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. diff --git a/tasks_gen/eval/G0006/seed/freight_calculator.clas.abap b/tasks_gen/eval/G0006/seed/freight_calculator.clas.abap new file mode 100644 index 0000000..098cc48 --- /dev/null +++ b/tasks_gen/eval/G0006/seed/freight_calculator.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0006/seed/freight_calculator.testclasses.abap b/tasks_gen/eval/G0006/seed/freight_calculator.testclasses.abap new file mode 100644 index 0000000..fb50217 --- /dev/null +++ b/tasks_gen/eval/G0006/seed/freight_calculator.testclasses.abap @@ -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. diff --git a/tasks_gen/eval/G0006/spec.md b/tasks_gen/eval/G0006/spec.md new file mode 100644 index 0000000..3c1b720 --- /dev/null +++ b/tasks_gen/eval/G0006/spec.md @@ -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. diff --git a/tasks_gen/eval/G0006/task.json b/tasks_gen/eval/G0006/task.json new file mode 100644 index 0000000..10b0599 --- /dev/null +++ b/tasks_gen/eval/G0006/task.json @@ -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" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0007/generation.json b/tasks_gen/eval/G0007/generation.json new file mode 100644 index 0000000..38cdfa9 --- /dev/null +++ b/tasks_gen/eval/G0007/generation.json @@ -0,0 +1,14 @@ +{ + "id": "G0007", + "pool": "eval", + "object_type": "CLAS", + "category": "F", + "attempts": [ + { + "stage": "validate", + "oracle": 100.0, + "null": 0 + } + ], + "accepted": true +} \ No newline at end of file diff --git a/tasks_gen/eval/G0007/hidden/t02_hidden.clas.abap b/tasks_gen/eval/G0007/hidden/t02_hidden.clas.abap new file mode 100644 index 0000000..b689ca6 --- /dev/null +++ b/tasks_gen/eval/G0007/hidden/t02_hidden.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0007/reference/loyalty_evaluator.clas.abap b/tasks_gen/eval/G0007/reference/loyalty_evaluator.clas.abap new file mode 100644 index 0000000..0213d17 --- /dev/null +++ b/tasks_gen/eval/G0007/reference/loyalty_evaluator.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0007/reference/loyalty_evaluator.testclasses.abap b/tasks_gen/eval/G0007/reference/loyalty_evaluator.testclasses.abap new file mode 100644 index 0000000..882bc5f --- /dev/null +++ b/tasks_gen/eval/G0007/reference/loyalty_evaluator.testclasses.abap @@ -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. diff --git a/tasks_gen/eval/G0007/seed/if_loyalty_rules.intf.abap b/tasks_gen/eval/G0007/seed/if_loyalty_rules.intf.abap new file mode 100644 index 0000000..75aaeb7 --- /dev/null +++ b/tasks_gen/eval/G0007/seed/if_loyalty_rules.intf.abap @@ -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. diff --git a/tasks_gen/eval/G0007/spec.md b/tasks_gen/eval/G0007/spec.md new file mode 100644 index 0000000..22e7242 --- /dev/null +++ b/tasks_gen/eval/G0007/spec.md @@ -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. diff --git a/tasks_gen/eval/G0007/task.json b/tasks_gen/eval/G0007/task.json new file mode 100644 index 0000000..ffe3d3b --- /dev/null +++ b/tasks_gen/eval/G0007/task.json @@ -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" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0008/generation.json b/tasks_gen/eval/G0008/generation.json new file mode 100644 index 0000000..d11fd91 --- /dev/null +++ b/tasks_gen/eval/G0008/generation.json @@ -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 +} \ No newline at end of file diff --git a/tasks_gen/eval/G0008/hidden/t07_hidden.clas.abap b/tasks_gen/eval/G0008/hidden/t07_hidden.clas.abap new file mode 100644 index 0000000..eae2e73 --- /dev/null +++ b/tasks_gen/eval/G0008/hidden/t07_hidden.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0008/reference/freight_calc.clas.abap b/tasks_gen/eval/G0008/reference/freight_calc.clas.abap new file mode 100644 index 0000000..a152a5d --- /dev/null +++ b/tasks_gen/eval/G0008/reference/freight_calc.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0008/reference/freight_calc.testclasses.abap b/tasks_gen/eval/G0008/reference/freight_calc.testclasses.abap new file mode 100644 index 0000000..29e6e7e --- /dev/null +++ b/tasks_gen/eval/G0008/reference/freight_calc.testclasses.abap @@ -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. diff --git a/tasks_gen/eval/G0008/seed/if_freight_calc.intf.abap b/tasks_gen/eval/G0008/seed/if_freight_calc.intf.abap new file mode 100644 index 0000000..dcbdd4e --- /dev/null +++ b/tasks_gen/eval/G0008/seed/if_freight_calc.intf.abap @@ -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. diff --git a/tasks_gen/eval/G0008/spec.md b/tasks_gen/eval/G0008/spec.md new file mode 100644 index 0000000..d54828e --- /dev/null +++ b/tasks_gen/eval/G0008/spec.md @@ -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. diff --git a/tasks_gen/eval/G0008/task.json b/tasks_gen/eval/G0008/task.json new file mode 100644 index 0000000..8760396 --- /dev/null +++ b/tasks_gen/eval/G0008/task.json @@ -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" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0009/generation.json b/tasks_gen/eval/G0009/generation.json new file mode 100644 index 0000000..0c0f6e0 --- /dev/null +++ b/tasks_gen/eval/G0009/generation.json @@ -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 +} \ No newline at end of file diff --git a/tasks_gen/eval/G0009/hidden/t02_hidden.clas.abap b/tasks_gen/eval/G0009/hidden/t02_hidden.clas.abap new file mode 100644 index 0000000..fff7739 --- /dev/null +++ b/tasks_gen/eval/G0009/hidden/t02_hidden.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0009/reference/shipping_cost_calc.clas.abap b/tasks_gen/eval/G0009/reference/shipping_cost_calc.clas.abap new file mode 100644 index 0000000..c78e710 --- /dev/null +++ b/tasks_gen/eval/G0009/reference/shipping_cost_calc.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0009/reference/shipping_cost_calc.testclasses.abap b/tasks_gen/eval/G0009/reference/shipping_cost_calc.testclasses.abap new file mode 100644 index 0000000..e51391a --- /dev/null +++ b/tasks_gen/eval/G0009/reference/shipping_cost_calc.testclasses.abap @@ -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. diff --git a/tasks_gen/eval/G0009/seed/if_shipping_cost.intf.abap b/tasks_gen/eval/G0009/seed/if_shipping_cost.intf.abap new file mode 100644 index 0000000..872ffdb --- /dev/null +++ b/tasks_gen/eval/G0009/seed/if_shipping_cost.intf.abap @@ -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. diff --git a/tasks_gen/eval/G0009/seed/shipping_cost_calc.clas.abap b/tasks_gen/eval/G0009/seed/shipping_cost_calc.clas.abap new file mode 100644 index 0000000..e26e271 --- /dev/null +++ b/tasks_gen/eval/G0009/seed/shipping_cost_calc.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0009/seed/shipping_cost_legacy.clas.abap b/tasks_gen/eval/G0009/seed/shipping_cost_legacy.clas.abap new file mode 100644 index 0000000..8ecf9c7 --- /dev/null +++ b/tasks_gen/eval/G0009/seed/shipping_cost_legacy.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0009/spec.md b/tasks_gen/eval/G0009/spec.md new file mode 100644 index 0000000..478eaf9 --- /dev/null +++ b/tasks_gen/eval/G0009/spec.md @@ -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. diff --git a/tasks_gen/eval/G0009/task.json b/tasks_gen/eval/G0009/task.json new file mode 100644 index 0000000..8e86116 --- /dev/null +++ b/tasks_gen/eval/G0009/task.json @@ -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" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0010/generation.json b/tasks_gen/eval/G0010/generation.json new file mode 100644 index 0000000..7b38b65 --- /dev/null +++ b/tasks_gen/eval/G0010/generation.json @@ -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 +} \ No newline at end of file diff --git a/tasks_gen/eval/G0010/hidden/t02_hidden.clas.abap b/tasks_gen/eval/G0010/hidden/t02_hidden.clas.abap new file mode 100644 index 0000000..84fe7c9 --- /dev/null +++ b/tasks_gen/eval/G0010/hidden/t02_hidden.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0010/reference/stock_cover_checker.clas.abap b/tasks_gen/eval/G0010/reference/stock_cover_checker.clas.abap new file mode 100644 index 0000000..5274b49 --- /dev/null +++ b/tasks_gen/eval/G0010/reference/stock_cover_checker.clas.abap @@ -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(). + IF sy-subrc = 0. + -remaining = -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(). + IF sy-subrc = 0. + rv_quantity = -remaining. + ENDIF. + ENDMETHOD. + + METHOD consume. + ASSIGN ct_stock[ material = normalize_material( iv_material ) + plant = normalize_plant( iv_plant ) ] TO FIELD-SYMBOL(). + IF sy-subrc = 0. + -remaining = -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. diff --git a/tasks_gen/eval/G0010/reference/stock_cover_checker.testclasses.abap b/tasks_gen/eval/G0010/reference/stock_cover_checker.testclasses.abap new file mode 100644 index 0000000..b8096a0 --- /dev/null +++ b/tasks_gen/eval/G0010/reference/stock_cover_checker.testclasses.abap @@ -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. diff --git a/tasks_gen/eval/G0010/seed/if_stock_cover.intf.abap b/tasks_gen/eval/G0010/seed/if_stock_cover.intf.abap new file mode 100644 index 0000000..7655f63 --- /dev/null +++ b/tasks_gen/eval/G0010/seed/if_stock_cover.intf.abap @@ -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. diff --git a/tasks_gen/eval/G0010/spec.md b/tasks_gen/eval/G0010/spec.md new file mode 100644 index 0000000..07310a0 --- /dev/null +++ b/tasks_gen/eval/G0010/spec.md @@ -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. diff --git a/tasks_gen/eval/G0010/task.json b/tasks_gen/eval/G0010/task.json new file mode 100644 index 0000000..c6cb9e3 --- /dev/null +++ b/tasks_gen/eval/G0010/task.json @@ -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" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0011/generation.json b/tasks_gen/eval/G0011/generation.json new file mode 100644 index 0000000..513ba27 --- /dev/null +++ b/tasks_gen/eval/G0011/generation.json @@ -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 +} \ No newline at end of file diff --git a/tasks_gen/eval/G0011/hidden/t14_hidden.clas.abap b/tasks_gen/eval/G0011/hidden/t14_hidden.clas.abap new file mode 100644 index 0000000..1afb676 --- /dev/null +++ b/tasks_gen/eval/G0011/hidden/t14_hidden.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0011/reference/cl_repl.clas.abap b/tasks_gen/eval/G0011/reference/cl_repl.clas.abap new file mode 100644 index 0000000..0e0cf45 --- /dev/null +++ b/tasks_gen/eval/G0011/reference/cl_repl.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0011/reference/repl_calc.func.abap b/tasks_gen/eval/G0011/reference/repl_calc.func.abap new file mode 100644 index 0000000..c980877 --- /dev/null +++ b/tasks_gen/eval/G0011/reference/repl_calc.func.abap @@ -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. diff --git a/tasks_gen/eval/G0011/reference/t14_test.clas.abap b/tasks_gen/eval/G0011/reference/t14_test.clas.abap new file mode 100644 index 0000000..f3e88a5 --- /dev/null +++ b/tasks_gen/eval/G0011/reference/t14_test.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0011/seed/repl_sample.func.abap b/tasks_gen/eval/G0011/seed/repl_sample.func.abap new file mode 100644 index 0000000..c4ffdbb --- /dev/null +++ b/tasks_gen/eval/G0011/seed/repl_sample.func.abap @@ -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. diff --git a/tasks_gen/eval/G0011/spec.md b/tasks_gen/eval/G0011/spec.md new file mode 100644 index 0000000..a8d22a8 --- /dev/null +++ b/tasks_gen/eval/G0011/spec.md @@ -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. diff --git a/tasks_gen/eval/G0011/task.json b/tasks_gen/eval/G0011/task.json new file mode 100644 index 0000000..5026559 --- /dev/null +++ b/tasks_gen/eval/G0011/task.json @@ -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" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0012/generation.json b/tasks_gen/eval/G0012/generation.json new file mode 100644 index 0000000..1b379d1 --- /dev/null +++ b/tasks_gen/eval/G0012/generation.json @@ -0,0 +1,14 @@ +{ + "id": "G0012", + "pool": "eval", + "object_type": "FUNC", + "category": "F", + "attempts": [ + { + "stage": "validate", + "oracle": 100.0, + "null": 0 + } + ], + "accepted": true +} \ No newline at end of file diff --git a/tasks_gen/eval/G0012/hidden/t14_hidden.clas.abap b/tasks_gen/eval/G0012/hidden/t14_hidden.clas.abap new file mode 100644 index 0000000..f8c7f01 --- /dev/null +++ b/tasks_gen/eval/G0012/hidden/t14_hidden.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0012/reference/prd_scrap.func.abap b/tasks_gen/eval/G0012/reference/prd_scrap.func.abap new file mode 100644 index 0000000..d574e4d --- /dev/null +++ b/tasks_gen/eval/G0012/reference/prd_scrap.func.abap @@ -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. diff --git a/tasks_gen/eval/G0012/reference/t14_test.clas.abap b/tasks_gen/eval/G0012/reference/t14_test.clas.abap new file mode 100644 index 0000000..d7660a8 --- /dev/null +++ b/tasks_gen/eval/G0012/reference/t14_test.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0012/seed/prd_sample.func.abap b/tasks_gen/eval/G0012/seed/prd_sample.func.abap new file mode 100644 index 0000000..e28e96d --- /dev/null +++ b/tasks_gen/eval/G0012/seed/prd_sample.func.abap @@ -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. diff --git a/tasks_gen/eval/G0012/spec.md b/tasks_gen/eval/G0012/spec.md new file mode 100644 index 0000000..0d813fd --- /dev/null +++ b/tasks_gen/eval/G0012/spec.md @@ -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. diff --git a/tasks_gen/eval/G0012/task.json b/tasks_gen/eval/G0012/task.json new file mode 100644 index 0000000..121b01d --- /dev/null +++ b/tasks_gen/eval/G0012/task.json @@ -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" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0013/generation.json b/tasks_gen/eval/G0013/generation.json new file mode 100644 index 0000000..cf364f4 --- /dev/null +++ b/tasks_gen/eval/G0013/generation.json @@ -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 +} \ No newline at end of file diff --git a/tasks_gen/eval/G0013/hidden/t21_hidden.clas.abap b/tasks_gen/eval/G0013/hidden/t21_hidden.clas.abap new file mode 100644 index 0000000..2a64110 --- /dev/null +++ b/tasks_gen/eval/G0013/hidden/t21_hidden.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0013/reference/repl_qty.func.abap b/tasks_gen/eval/G0013/reference/repl_qty.func.abap new file mode 100644 index 0000000..c2851f1 --- /dev/null +++ b/tasks_gen/eval/G0013/reference/repl_qty.func.abap @@ -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. diff --git a/tasks_gen/eval/G0013/reference/t21_test.clas.abap b/tasks_gen/eval/G0013/reference/t21_test.clas.abap new file mode 100644 index 0000000..925a275 --- /dev/null +++ b/tasks_gen/eval/G0013/reference/t21_test.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0013/seed/repl_qty.func.abap b/tasks_gen/eval/G0013/seed/repl_qty.func.abap new file mode 100644 index 0000000..9780591 --- /dev/null +++ b/tasks_gen/eval/G0013/seed/repl_qty.func.abap @@ -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. diff --git a/tasks_gen/eval/G0013/seed/repl_qty_old.func.abap b/tasks_gen/eval/G0013/seed/repl_qty_old.func.abap new file mode 100644 index 0000000..df16f6d --- /dev/null +++ b/tasks_gen/eval/G0013/seed/repl_qty_old.func.abap @@ -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. diff --git a/tasks_gen/eval/G0013/spec.md b/tasks_gen/eval/G0013/spec.md new file mode 100644 index 0000000..f0fb4ac --- /dev/null +++ b/tasks_gen/eval/G0013/spec.md @@ -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. diff --git a/tasks_gen/eval/G0013/task.json b/tasks_gen/eval/G0013/task.json new file mode 100644 index 0000000..57e23dc --- /dev/null +++ b/tasks_gen/eval/G0013/task.json @@ -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" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0014/generation.json b/tasks_gen/eval/G0014/generation.json new file mode 100644 index 0000000..6f184a7 --- /dev/null +++ b/tasks_gen/eval/G0014/generation.json @@ -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 +} \ No newline at end of file diff --git a/tasks_gen/eval/G0014/hidden/t15_hidden.clas.abap b/tasks_gen/eval/G0014/hidden/t15_hidden.clas.abap new file mode 100644 index 0000000..10918f0 --- /dev/null +++ b/tasks_gen/eval/G0014/hidden/t15_hidden.clas.abap @@ -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 TYPE STANDARD TABLE. + IF ir_data IS BOUND. + ASSIGN ir_data->* TO . + rv_lines = lines( ). + ENDIF. + ENDMETHOD. + + METHOD value_of. + FIELD-SYMBOLS TYPE STANDARD TABLE. + ASSIGN ir_data->* TO . + ASSIGN [ iv_row ] TO FIELD-SYMBOL(). + ASSIGN COMPONENT iv_col OF STRUCTURE TO FIELD-SYMBOL(). + cl_abap_unit_assert=>assert_subrc( exp = 0 msg = |Column { iv_col } missing| ). + rv_value = condense( CONV string( ) ). + " 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. diff --git a/tasks_gen/eval/G0014/reference/stock_bal.prog.abap b/tasks_gen/eval/G0014/reference/stock_bal.prog.abap new file mode 100644 index 0000000..6253c89 --- /dev/null +++ b/tasks_gen/eval/G0014/reference/stock_bal.prog.abap @@ -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(). + -balance = -receipt_qty - -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. diff --git a/tasks_gen/eval/G0014/seed/movmt.tabl.asabap b/tasks_gen/eval/G0014/seed/movmt.tabl.asabap new file mode 100644 index 0000000..e05d163 --- /dev/null +++ b/tasks_gen/eval/G0014/seed/movmt.tabl.asabap @@ -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; +} diff --git a/tasks_gen/eval/G0014/seed/t15_seed.clas.abap b/tasks_gen/eval/G0014/seed/t15_seed.clas.abap new file mode 100644 index 0000000..0a91819 --- /dev/null +++ b/tasks_gen/eval/G0014/seed/t15_seed.clas.abap @@ -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. diff --git a/tasks_gen/eval/G0014/spec.md b/tasks_gen/eval/G0014/spec.md new file mode 100644 index 0000000..adc0912 --- /dev/null +++ b/tasks_gen/eval/G0014/spec.md @@ -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. diff --git a/tasks_gen/eval/G0014/task.json b/tasks_gen/eval/G0014/task.json new file mode 100644 index 0000000..602fd43 --- /dev/null +++ b/tasks_gen/eval/G0014/task.json @@ -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" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0015/generation.json b/tasks_gen/eval/G0015/generation.json new file mode 100644 index 0000000..ee8ad6e --- /dev/null +++ b/tasks_gen/eval/G0015/generation.json @@ -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 +} \ No newline at end of file diff --git a/tasks_gen/eval/G0015/hidden/bill_age_test.clas.abap b/tasks_gen/eval/G0015/hidden/bill_age_test.clas.abap new file mode 100644 index 0000000..29f9565 --- /dev/null +++ b/tasks_gen/eval/G0015/hidden/bill_age_test.clas.abap @@ -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 TYPE STANDARD TABLE. + IF ir_data IS BOUND. + ASSIGN ir_data->* TO . + rv_lines = lines( ). + ENDIF. + ENDMETHOD. + + METHOD value_of. + FIELD-SYMBOLS TYPE STANDARD TABLE. + ASSIGN ir_data->* TO . + ASSIGN [ iv_row ] TO FIELD-SYMBOL(). + ASSIGN COMPONENT iv_col OF STRUCTURE TO FIELD-SYMBOL(). + cl_abap_unit_assert=>assert_subrc( exp = 0 msg = |Column { iv_col } is missing| ). + rv_value = condense( CONV string( ) ). + 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. diff --git a/tasks_gen/eval/G0015/reference/bill_age.prog.abap b/tasks_gen/eval/G0015/reference/bill_age.prog.abap new file mode 100644 index 0000000..3b18c69 --- /dev/null +++ b/tasks_gen/eval/G0015/reference/bill_age.prog.abap @@ -0,0 +1,95 @@ +REPORT {{p}}bill_age. + +DATA gv_customer TYPE {{p}}bill-customer. + +PARAMETERS p_date TYPE d OBLIGATORY. +SELECT-OPTIONS s_cust FOR gv_customer. + +CLASS lcl_report DEFINITION FINAL. + PUBLIC SECTION. + TYPES: + BEGIN OF ty_line, + customer TYPE {{p}}bill-customer, + open_count TYPE i, + open_amount TYPE {{p}}bill-amount, + overdue_amount TYPE {{p}}bill-amount, + END OF ty_line, + tt_line TYPE STANDARD TABLE OF ty_line WITH EMPTY KEY, + tt_customer TYPE RANGE OF {{p}}bill-customer. + METHODS select_open_items + IMPORTING iv_date TYPE d + it_customer TYPE tt_customer + RETURNING VALUE(rt_lines) TYPE tt_line. + METHODS display + CHANGING ct_lines TYPE tt_line. +ENDCLASS. + +CLASS lcl_report IMPLEMENTATION. + METHOD select_open_items. + SELECT customer, + COUNT( * ) AS open_count, + SUM( amount ) AS open_amount, + SUM( CASE WHEN due_date < @iv_date THEN amount ELSE 0 END ) AS overdue_amount + FROM {{p}}bill + WHERE paid <> 'X' + AND customer IN @it_customer + GROUP BY customer + ORDER BY open_amount DESCENDING + INTO CORRESPONDING FIELDS OF TABLE @rt_lines. + 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_open_items( iv_date = p_date it_customer = s_cust[] ). + 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 open_invoices_only FOR TESTING. + METHODS overdue_amount FOR TESTING. +ENDCLASS. + +CLASS ltc_report IMPLEMENTATION. + METHOD class_setup. + go_env = cl_osql_test_environment=>create( i_dependency_list = VALUE #( ( '{{P}}BILL' ) ) ). + DATA lt_bill TYPE STANDARD TABLE OF {{p}}bill WITH EMPTY KEY. + lt_bill = VALUE #( ( bill_id = 1 customer = 'A' due_date = '20260101' amount = 10 paid = ' ' ) + ( bill_id = 2 customer = 'A' due_date = '20260301' amount = 5 paid = ' ' ) + ( bill_id = 3 customer = 'B' due_date = '20260101' amount = 40 paid = 'X' ) ). + go_env->insert_test_data( lt_bill ). + ENDMETHOD. + + METHOD class_teardown. + go_env->destroy( ). + ENDMETHOD. + + METHOD open_invoices_only. + DATA(lt_lines) = NEW lcl_report( )->select_open_items( iv_date = '20260101' + it_customer = VALUE #( ) ). + cl_abap_unit_assert=>assert_equals( exp = 1 act = lines( lt_lines ) ). + cl_abap_unit_assert=>assert_equals( exp = 'A' act = lt_lines[ 1 ]-customer ). + cl_abap_unit_assert=>assert_equals( exp = 2 act = lt_lines[ 1 ]-open_count ). + cl_abap_unit_assert=>assert_equals( exp = CONV {{p}}bill-amount( 15 ) act = lt_lines[ 1 ]-open_amount ). + ENDMETHOD. + + METHOD overdue_amount. + DATA(lt_lines) = NEW lcl_report( )->select_open_items( iv_date = '20260201' + it_customer = VALUE #( ) ). + cl_abap_unit_assert=>assert_equals( exp = CONV {{p}}bill-amount( 10 ) + act = lt_lines[ 1 ]-overdue_amount ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0015/seed/bill.tabl.asabap b/tasks_gen/eval/G0015/seed/bill.tabl.asabap new file mode 100644 index 0000000..e3638ac --- /dev/null +++ b/tasks_gen/eval/G0015/seed/bill.tabl.asabap @@ -0,0 +1,13 @@ +@EndUserText.label : 'Invoices' +@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE +@AbapCatalog.tableCategory : #TRANSPARENT +@AbapCatalog.deliveryClass : #A +@AbapCatalog.dataMaintenance : #RESTRICTED +define table {{p}}bill { + key client : abap.clnt not null; + key bill_id : abap.int4 not null; + customer : abap.char(10); + due_date : abap.dats; + amount : abap.dec(15,2); + paid : abap.char(1); +} diff --git a/tasks_gen/eval/G0015/seed/bill_seed.clas.abap b/tasks_gen/eval/G0015/seed/bill_seed.clas.abap new file mode 100644 index 0000000..faea48a --- /dev/null +++ b/tasks_gen/eval/G0015/seed/bill_seed.clas.abap @@ -0,0 +1,19 @@ +CLASS {{p}}bill_seed DEFINITION PUBLIC FINAL CREATE PUBLIC. + PUBLIC SECTION. + INTERFACES if_oo_adt_classrun. +ENDCLASS. + +CLASS {{p}}bill_seed IMPLEMENTATION. + METHOD if_oo_adt_classrun~main. + DELETE FROM {{p}}bill. + INSERT {{p}}bill FROM TABLE @( VALUE #( + ( bill_id = 1 customer = 'C001' due_date = '20260210' amount = '100.00' paid = ' ' ) + ( bill_id = 2 customer = 'C001' due_date = '20260410' amount = '50.00' paid = ' ' ) + ( bill_id = 3 customer = 'C002' due_date = '20260105' amount = '300.00' paid = ' ' ) + ( bill_id = 4 customer = 'C002' due_date = '20260501' amount = '200.00' paid = 'X' ) + ( bill_id = 5 customer = 'C003' due_date = '20260320' amount = '75.50' paid = ' ' ) + ( bill_id = 6 customer = 'C001' due_date = '20260101' amount = '25.00' paid = 'X' ) + ( bill_id = 7 customer = 'C004' due_date = '20260615' amount = '500.00' paid = ' ' ) ) ). + out->write( sy-dbcnt ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0015/spec.md b/tasks_gen/eval/G0015/spec.md new file mode 100644 index 0000000..05f4301 --- /dev/null +++ b/tasks_gen/eval/G0015/spec.md @@ -0,0 +1,42 @@ +# 1. Goal +Show the open invoice amounts for each customer in an ALV list. The accounting +team uses the list to find the customers that owe money and to see how much of +the money is overdue. + +# 2. Open questions +None. + +# 3. Context +- The table {{P}}BILL exists in package $TMP. It contains data. + Fields: BILL_ID (INT4, key), CUSTOMER (CHAR 10), DUE_DATE (DATS), + AMOUNT (DEC 15,2), PAID (CHAR 1). +- PAID is 'X' if the customer paid the invoice. PAID is ' ' if the invoice is + still open. + +# 4. Contract +- Create the executable program {{P}}BILL_AGE in package $TMP. +- Selection screen: + - Parameter P_DATE, type DATS, mandatory: the date that you compare with the + due date. + - Select-option S_CUST for the customer. +- Show the result with CL_SALV_TABLE. The output table has these columns, with + these names: CUSTOMER, OPEN_COUNT, OPEN_AMOUNT, OVERDUE_AMOUNT. + +# 5. Business rules +1. Include an invoice only if PAID is not 'X' and CUSTOMER is in S_CUST. +2. Show one line for each customer that has at least one included invoice. +3. OPEN_COUNT is the number of included invoices of the customer. +4. OPEN_AMOUNT is the sum of AMOUNT of the included invoices of the customer. +5. OVERDUE_AMOUNT is the sum of AMOUNT of the included invoices of the customer + whose DUE_DATE is before P_DATE. +6. Sort the lines by OPEN_AMOUNT, the highest amount first. + +# 6. Constraints +- Release target: 7.58. +- Coding standards: Clean ABAP. Read the invoice data with one database access. +- Out of scope: do not change {{P}}BILL. + +# 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. diff --git a/tasks_gen/eval/G0015/task.json b/tasks_gen/eval/G0015/task.json new file mode 100644 index 0000000..caedc59 --- /dev/null +++ b/tasks_gen/eval/G0015/task.json @@ -0,0 +1,60 @@ +{ + "id": "G0015", + "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}}BILL", + "file": "seed/bill.tabl.asabap", + "description": "Invoices with due date and paid flag" + }, + { + "type": "CLAS", + "name": "{{P}}BILL_SEED", + "file": "seed/bill_seed.clas.abap", + "description": "Invoice seed data", + "run": true + } + ], + "contract": [ + { + "type": "PROG", + "name": "{{P}}BILL_AGE", + "parameters": [ + "p_date", + "s_cust" + ] + } + ], + "out_of_scope": [ + "{{P}}BILL" + ], + "hidden_tests": [ + { + "type": "CLAS", + "name": "{{P}}BILL_AGE_TEST", + "file": "hidden/bill_age_test.clas.abap", + "description": "Hidden tests for the open invoice report" + } + ], + "reference": [ + { + "type": "PROG", + "name": "{{P}}BILL_AGE", + "file": "reference/bill_age.prog.abap", + "description": "Open invoice amounts per customer" + } + ], + "craft_checks": [ + "db_operation_in_loop", + "method_length" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0016/generation.json b/tasks_gen/eval/G0016/generation.json new file mode 100644 index 0000000..796f3fe --- /dev/null +++ b/tasks_gen/eval/G0016/generation.json @@ -0,0 +1,24 @@ +{ + "id": "G0016", + "pool": "eval", + "object_type": "PROG", + "category": "E", + "attempts": [ + { + "stage": "validate", + "oracle": null, + "null": null + }, + { + "stage": "validate", + "oracle": 0, + "null": 0 + }, + { + "stage": "validate", + "oracle": 100.0, + "null": 0 + } + ], + "accepted": true +} \ No newline at end of file diff --git a/tasks_gen/eval/G0016/hidden/t15_hidden.clas.abap b/tasks_gen/eval/G0016/hidden/t15_hidden.clas.abap new file mode 100644 index 0000000..4b69376 --- /dev/null +++ b/tasks_gen/eval/G0016/hidden/t15_hidden.clas.abap @@ -0,0 +1,186 @@ +CLASS {{p}}t15_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC + FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + TYPES tt_matnr TYPE RANGE OF {{p}}move-material. + + METHODS run + IMPORTING iv_from TYPE d + iv_to TYPE d + it_matnr TYPE tt_matnr OPTIONAL + RETURNING VALUE(rr_data) TYPE REF TO data. + + METHODS lines_of + IMPORTING ir_data TYPE REF TO data + RETURNING VALUE(rv_lines) TYPE i. + + METHODS text_of + IMPORTING ir_data TYPE REF TO data + iv_row TYPE i + iv_col TYPE string + RETURNING VALUE(rv_text) TYPE string. + + METHODS qty_of + IMPORTING ir_data TYPE REF TO data + iv_row TYPE i + RETURNING VALUE(rv_qty) TYPE {{p}}move-quantity. + + METHODS count_of + IMPORTING ir_data TYPE REF TO data + iv_row TYPE i + RETURNING VALUE(rv_count) TYPE i. + + METHODS all_groups FOR TESTING. + METHODS sort_order FOR TESTING. + METHODS net_quantity FOR TESTING. + METHODS move_count FOR TESTING. + METHODS material_filter FOR TESTING. + METHODS date_from_filter FOR TESTING. + METHODS date_to_filter FOR TESTING. + METHODS no_matching_data 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_net + WITH p_from = iv_from + WITH p_to = iv_to + WITH s_matnr IN it_matnr + 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 TYPE STANDARD TABLE. + IF ir_data IS BOUND. + ASSIGN ir_data->* TO . + rv_lines = lines( ). + ENDIF. + ENDMETHOD. + + METHOD text_of. + FIELD-SYMBOLS TYPE STANDARD TABLE. + ASSIGN ir_data->* TO . + ASSIGN [ iv_row ] TO FIELD-SYMBOL(). + cl_abap_unit_assert=>assert_subrc( exp = 0 msg = |Row { iv_row } is missing| ). + ASSIGN COMPONENT iv_col OF STRUCTURE TO FIELD-SYMBOL(). + cl_abap_unit_assert=>assert_subrc( exp = 0 msg = |Column { iv_col } is missing| ). + DATA lv_text TYPE string. + lv_text = . + rv_text = condense( lv_text ). + ENDMETHOD. + + METHOD qty_of. + FIELD-SYMBOLS TYPE STANDARD TABLE. + ASSIGN ir_data->* TO . + ASSIGN [ iv_row ] TO FIELD-SYMBOL(). + cl_abap_unit_assert=>assert_subrc( exp = 0 msg = |Row { iv_row } is missing| ). + ASSIGN COMPONENT 'NET_QTY' OF STRUCTURE TO FIELD-SYMBOL(). + cl_abap_unit_assert=>assert_subrc( exp = 0 msg = 'Column NET_QTY is missing' ). + DATA lv_qty TYPE {{p}}move-quantity. + lv_qty = . + rv_qty = lv_qty. + ENDMETHOD. + + METHOD count_of. + FIELD-SYMBOLS TYPE STANDARD TABLE. + ASSIGN ir_data->* TO . + ASSIGN [ iv_row ] TO FIELD-SYMBOL(). + cl_abap_unit_assert=>assert_subrc( exp = 0 msg = |Row { iv_row } is missing| ). + ASSIGN COMPONENT 'MOVE_COUNT' OF STRUCTURE TO FIELD-SYMBOL(). + cl_abap_unit_assert=>assert_subrc( exp = 0 msg = 'Column MOVE_COUNT is missing' ). + DATA lv_count TYPE i. + lv_count = . + rv_count = lv_count. + ENDMETHOD. + + METHOD all_groups. + cl_abap_unit_assert=>assert_equals( + exp = 4 + act = lines_of( run( iv_from = '20260101' iv_to = '20261231' ) ) ). + ENDMETHOD. + + METHOD sort_order. + DATA(lr_data) = run( iv_from = '20260101' iv_to = '20261231' ). + cl_abap_unit_assert=>assert_equals( + exp = `MAT-001` + act = text_of( ir_data = lr_data iv_row = 1 iv_col = 'MATERIAL' ) ). + cl_abap_unit_assert=>assert_equals( + exp = `1000` + act = text_of( ir_data = lr_data iv_row = 1 iv_col = 'PLANT' ) ). + cl_abap_unit_assert=>assert_equals( + exp = `2000` + act = text_of( ir_data = lr_data iv_row = 2 iv_col = 'PLANT' ) ). + cl_abap_unit_assert=>assert_equals( + exp = `MAT-002` + act = text_of( ir_data = lr_data iv_row = 3 iv_col = 'MATERIAL' ) ). + cl_abap_unit_assert=>assert_equals( + exp = `MAT-002` + act = text_of( ir_data = lr_data iv_row = 4 iv_col = 'MATERIAL' ) ). + ENDMETHOD. + + METHOD net_quantity. + DATA(lr_data) = run( iv_from = '20260101' iv_to = '20261231' ). + cl_abap_unit_assert=>assert_equals( + exp = CONV {{p}}move-quantity( 120 ) + act = qty_of( ir_data = lr_data iv_row = 1 ) ). + cl_abap_unit_assert=>assert_equals( + exp = CONV {{p}}move-quantity( -40 ) + act = qty_of( ir_data = lr_data iv_row = 2 ) ). + cl_abap_unit_assert=>assert_equals( + exp = CONV {{p}}move-quantity( -15 ) + act = qty_of( ir_data = lr_data iv_row = 4 ) ). + ENDMETHOD. + + METHOD move_count. + DATA(lr_data) = run( iv_from = '20260101' iv_to = '20261231' ). + cl_abap_unit_assert=>assert_equals( exp = 3 act = count_of( ir_data = lr_data iv_row = 1 ) ). + cl_abap_unit_assert=>assert_equals( exp = 1 act = count_of( ir_data = lr_data iv_row = 2 ) ). + cl_abap_unit_assert=>assert_equals( exp = 2 act = count_of( ir_data = lr_data iv_row = 4 ) ). + ENDMETHOD. + + METHOD material_filter. + DATA(lr_data) = run( iv_from = '20260101' iv_to = '20261231' + it_matnr = VALUE #( ( sign = 'I' option = 'EQ' low = 'MAT-002' ) ) ). + cl_abap_unit_assert=>assert_equals( exp = 2 act = lines_of( lr_data ) ). + cl_abap_unit_assert=>assert_equals( + exp = `MAT-002` + act = text_of( ir_data = lr_data iv_row = 1 iv_col = 'MATERIAL' ) ). + cl_abap_unit_assert=>assert_equals( + exp = `1000` + act = text_of( ir_data = lr_data iv_row = 1 iv_col = 'PLANT' ) ). + cl_abap_unit_assert=>assert_equals( + exp = CONV {{p}}move-quantity( 200 ) + act = qty_of( ir_data = lr_data iv_row = 1 ) ). + ENDMETHOD. + + METHOD date_from_filter. + DATA(lr_data) = run( iv_from = '20260201' iv_to = '20261231' ). + cl_abap_unit_assert=>assert_equals( exp = 1 act = lines_of( lr_data ) ). + cl_abap_unit_assert=>assert_equals( + exp = `MAT-002` + act = text_of( ir_data = lr_data iv_row = 1 iv_col = 'MATERIAL' ) ). + cl_abap_unit_assert=>assert_equals( + exp = CONV {{p}}move-quantity( 200 ) + act = qty_of( ir_data = lr_data iv_row = 1 ) ). + ENDMETHOD. + + METHOD date_to_filter. + DATA(lr_data) = run( iv_from = '20260101' iv_to = '20260131' ). + cl_abap_unit_assert=>assert_equals( exp = 3 act = lines_of( lr_data ) ). + cl_abap_unit_assert=>assert_equals( + exp = CONV {{p}}move-quantity( -15 ) + act = qty_of( ir_data = lr_data iv_row = 3 ) ). + ENDMETHOD. + + METHOD no_matching_data. + cl_abap_unit_assert=>assert_equals( + exp = 0 + act = lines_of( run( iv_from = '20270101' iv_to = '20271231' ) ) ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0016/reference/stock_net.prog.abap b/tasks_gen/eval/G0016/reference/stock_net.prog.abap new file mode 100644 index 0000000..a753517 --- /dev/null +++ b/tasks_gen/eval/G0016/reference/stock_net.prog.abap @@ -0,0 +1,132 @@ +REPORT {{p}}stock_net. + +TYPES: BEGIN OF ty_line, + material TYPE {{p}}move-material, + plant TYPE {{p}}move-plant, + net_qty TYPE {{p}}move-quantity, + move_count TYPE i, + END OF ty_line, + tt_line TYPE STANDARD TABLE OF ty_line WITH EMPTY KEY, + tt_acc TYPE SORTED TABLE OF ty_line WITH UNIQUE KEY material plant, + tt_matnr TYPE RANGE OF {{p}}move-material. + +DATA gv_matnr TYPE {{p}}move-material. + +PARAMETERS: p_from TYPE d OBLIGATORY, + p_to TYPE d OBLIGATORY. +SELECT-OPTIONS: s_matnr FOR gv_matnr. + +CLASS lcl_report DEFINITION FINAL. + PUBLIC SECTION. + METHODS select_totals + IMPORTING iv_from TYPE d + iv_to TYPE d + it_matnr TYPE tt_matnr + RETURNING VALUE(rt_lines) TYPE tt_line. + METHODS display + CHANGING ct_lines TYPE tt_line. +ENDCLASS. + +CLASS lcl_report IMPLEMENTATION. + METHOD select_totals. + TYPES: BEGIN OF ty_group, + material TYPE {{p}}move-material, + plant TYPE {{p}}move-plant, + move_type TYPE {{p}}move-move_type, + qty TYPE {{p}}move-quantity, + cnt TYPE i, + END OF ty_group. + DATA lt_group TYPE STANDARD TABLE OF ty_group WITH EMPTY KEY. + + SELECT material, plant, move_type, SUM( quantity ) AS qty, COUNT(*) AS cnt + FROM {{p}}move + WHERE move_date >= @iv_from + AND move_date <= @iv_to + AND material IN @it_matnr + GROUP BY material, plant, move_type + INTO CORRESPONDING FIELDS OF TABLE @lt_group. + + DATA lt_acc TYPE tt_acc. + LOOP AT lt_group INTO DATA(ls_group). + READ TABLE lt_acc ASSIGNING FIELD-SYMBOL() + WITH TABLE KEY material = ls_group-material plant = ls_group-plant. + IF sy-subrc <> 0. + INSERT VALUE ty_line( material = ls_group-material + plant = ls_group-plant ) + INTO TABLE lt_acc ASSIGNING . + ENDIF. + IF ls_group-move_type = 'GR'. + -net_qty = -net_qty + ls_group-qty. + ELSE. + -net_qty = -net_qty - ls_group-qty. + ENDIF. + -move_count = -move_count + ls_group-cnt. + ENDLOOP. + rt_lines = lt_acc. + 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_msg). + MESSAGE lx_msg TYPE 'I' DISPLAY LIKE 'E'. + ENDTRY. + ENDMETHOD. +ENDCLASS. + +START-OF-SELECTION. + DATA(go_report) = NEW lcl_report( ). + DATA(gt_lines) = go_report->select_totals( iv_from = p_from + iv_to = p_to + it_matnr = s_matnr[] ). + 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 net_quantity FOR TESTING. + METHODS material_filter FOR TESTING. +ENDCLASS. + +CLASS ltc_report IMPLEMENTATION. + METHOD class_setup. + go_env = cl_osql_test_environment=>create( i_dependency_list = VALUE #( ( '{{P}}MOVE' ) ) ). + DATA lt_move TYPE STANDARD TABLE OF {{p}}move WITH EMPTY KEY. + lt_move = VALUE #( + ( mov_id = 1 material = 'MAT-001' plant = '1000' move_date = '20260110' + move_type = 'GR' quantity = '100.000' ) + ( mov_id = 2 material = 'MAT-001' plant = '1000' move_date = '20260115' + move_type = 'GI' quantity = '30.000' ) + ( mov_id = 3 material = 'MAT-002' plant = '1000' move_date = '20260201' + move_type = 'GR' quantity = '200.000' ) ). + go_env->insert_test_data( lt_move ). + ENDMETHOD. + + METHOD class_teardown. + go_env->destroy( ). + ENDMETHOD. + + METHOD net_quantity. + DATA(lt_lines) = NEW lcl_report( )->select_totals( iv_from = '20260101' + iv_to = '20261231' + it_matnr = VALUE #( ) ). + cl_abap_unit_assert=>assert_equals( exp = 2 act = lines( lt_lines ) ). + cl_abap_unit_assert=>assert_equals( exp = `MAT-001` act = condense( lt_lines[ 1 ]-material ) ). + cl_abap_unit_assert=>assert_equals( exp = CONV {{p}}move-quantity( 70 ) act = lt_lines[ 1 ]-net_qty ). + cl_abap_unit_assert=>assert_equals( exp = 2 act = lt_lines[ 1 ]-move_count ). + ENDMETHOD. + + METHOD material_filter. + DATA(lt_lines) = NEW lcl_report( )->select_totals( + iv_from = '20260101' + iv_to = '20261231' + it_matnr = VALUE #( ( sign = 'I' option = 'EQ' low = 'MAT-002' ) ) ). + cl_abap_unit_assert=>assert_equals( exp = 1 act = lines( lt_lines ) ). + cl_abap_unit_assert=>assert_equals( exp = CONV {{p}}move-quantity( 200 ) act = lt_lines[ 1 ]-net_qty ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0016/seed/move.tabl.asabap b/tasks_gen/eval/G0016/seed/move.tabl.asabap new file mode 100644 index 0000000..782bf58 --- /dev/null +++ b/tasks_gen/eval/G0016/seed/move.tabl.asabap @@ -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}}move { + key client : abap.clnt not null; + key mov_id : abap.int4 not null; + material : abap.char(10); + plant : abap.char(4); + move_date : abap.dats; + move_type : abap.char(2); + quantity : abap.dec(15,3); +} diff --git a/tasks_gen/eval/G0016/seed/seed_move.clas.abap b/tasks_gen/eval/G0016/seed/seed_move.clas.abap new file mode 100644 index 0000000..bf6d07e --- /dev/null +++ b/tasks_gen/eval/G0016/seed/seed_move.clas.abap @@ -0,0 +1,20 @@ +CLASS {{p}}seed_move DEFINITION PUBLIC FINAL CREATE PUBLIC. + PUBLIC SECTION. + INTERFACES if_oo_adt_classrun. +ENDCLASS. + +CLASS {{p}}seed_move IMPLEMENTATION. + METHOD if_oo_adt_classrun~main. + DELETE FROM {{p}}move. + INSERT {{p}}move FROM TABLE @( VALUE #( + ( mov_id = 1 material = 'MAT-001' plant = '1000' move_date = '20260110' move_type = 'GR' quantity = '100.000' ) + ( mov_id = 2 material = 'MAT-001' plant = '1000' move_date = '20260120' move_type = 'GR' quantity = '50.000' ) + ( mov_id = 3 material = 'MAT-001' plant = '1000' move_date = '20260115' move_type = 'GI' quantity = '30.000' ) + ( mov_id = 4 material = 'MAT-001' plant = '2000' move_date = '20260112' move_type = 'GI' quantity = '40.000' ) + ( mov_id = 5 material = 'MAT-002' plant = '1000' move_date = '20260201' move_type = 'GR' quantity = '200.000' ) + ( mov_id = 6 material = 'MAT-002' plant = '2000' move_date = '20260115' move_type = 'GR' quantity = '10.000' ) + ( mov_id = 7 material = 'MAT-002' plant = '2000' move_date = '20260116' move_type = 'GI' quantity = '25.000' ) + ( mov_id = 8 material = 'MAT-003' plant = '1000' move_date = '20251231' move_type = 'GR' quantity = '500.000' ) ) ). + out->write( sy-dbcnt ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0016/seed/stock_net.prog.abap b/tasks_gen/eval/G0016/seed/stock_net.prog.abap new file mode 100644 index 0000000..323878c --- /dev/null +++ b/tasks_gen/eval/G0016/seed/stock_net.prog.abap @@ -0,0 +1,82 @@ +REPORT {{p}}stock_net. + +TYPES: BEGIN OF ty_key, + material TYPE {{p}}move-material, + plant TYPE {{p}}move-plant, + END OF ty_key, + BEGIN OF ty_out, + material TYPE {{p}}move-material, + plant TYPE {{p}}move-plant, + net_qty TYPE {{p}}move-quantity, + move_count TYPE i, + END OF ty_out. + +DATA: gv_matnr TYPE {{p}}move-material, + gv_from TYPE d, + gv_to TYPE d, + gv_gr TYPE {{p}}move-quantity, + gv_gi TYPE {{p}}move-quantity, + gv_cnt TYPE i, + gs_key TYPE ty_key, + gs_out TYPE ty_out, + gt_keys TYPE STANDARD TABLE OF ty_key, + gt_out TYPE STANDARD TABLE OF ty_out, + go_alv TYPE REF TO cl_salv_table, + gx_msg TYPE REF TO cx_salv_msg. + +PARAMETERS: p_from TYPE d OBLIGATORY, + p_to TYPE d OBLIGATORY. +SELECT-OPTIONS: s_matnr FOR gv_matnr. + +START-OF-SELECTION. + gv_from = p_from. + gv_to = p_to. + + SELECT DISTINCT material, plant + FROM {{p}}move + INTO TABLE gt_keys + WHERE move_date >= gv_from + AND move_date <= gv_to + AND material IN s_matnr + ORDER BY material, plant. + + LOOP AT gt_keys INTO gs_key. + CLEAR: gv_gr, gv_gi, gv_cnt, gs_out. + + SELECT SUM( quantity ) FROM {{p}}move INTO gv_gr + WHERE material = gs_key-material + AND plant = gs_key-plant + AND move_type = 'GR' + AND move_date >= gv_from + AND move_date <= gv_to. + + SELECT SUM( quantity ) FROM {{p}}move INTO gv_gi + WHERE material = gs_key-material + AND plant = gs_key-plant + AND move_type = 'GI' + AND move_date >= gv_from + AND move_date <= gv_to. + + SELECT COUNT(*) FROM {{p}}move INTO gv_cnt + WHERE material = gs_key-material + AND plant = gs_key-plant + AND move_date >= gv_from + AND move_date <= gv_to. + + gs_out-material = gs_key-material. + gs_out-plant = gs_key-plant. + gs_out-net_qty = gv_gr - gv_gi. + gs_out-move_count = gv_cnt. + APPEND gs_out TO gt_out. + ENDLOOP. + + TRY. + CALL METHOD cl_salv_table=>factory + IMPORTING + r_salv_table = go_alv + CHANGING + t_table = gt_out. + CALL METHOD go_alv->display. + CATCH cx_salv_msg INTO gx_msg. + MESSAGE gx_msg TYPE 'I' DISPLAY LIKE 'E'. + ENDTRY. diff --git a/tasks_gen/eval/G0016/seed/stock_net_old.prog.abap b/tasks_gen/eval/G0016/seed/stock_net_old.prog.abap new file mode 100644 index 0000000..43293cf --- /dev/null +++ b/tasks_gen/eval/G0016/seed/stock_net_old.prog.abap @@ -0,0 +1,82 @@ +REPORT {{p}}stock_net_old. + +TYPES: BEGIN OF ty_key, + material TYPE {{p}}move-material, + plant TYPE {{p}}move-plant, + END OF ty_key, + BEGIN OF ty_out, + material TYPE {{p}}move-material, + plant TYPE {{p}}move-plant, + net_qty TYPE {{p}}move-quantity, + move_count TYPE i, + END OF ty_out. + +DATA: gv_matnr TYPE {{p}}move-material, + gv_from TYPE d, + gv_to TYPE d, + gv_gr TYPE {{p}}move-quantity, + gv_gi TYPE {{p}}move-quantity, + gv_cnt TYPE i, + gs_key TYPE ty_key, + gs_out TYPE ty_out, + gt_keys TYPE STANDARD TABLE OF ty_key, + gt_out TYPE STANDARD TABLE OF ty_out, + go_alv TYPE REF TO cl_salv_table, + gx_msg TYPE REF TO cx_salv_msg. + +PARAMETERS: p_from TYPE d OBLIGATORY, + p_to TYPE d OBLIGATORY. +SELECT-OPTIONS: s_matnr FOR gv_matnr. + +START-OF-SELECTION. + gv_from = p_from. + gv_to = p_to. + + SELECT DISTINCT material plant + FROM {{p}}move + INTO TABLE gt_keys + WHERE move_date >= gv_from + AND move_date <= gv_to + AND material IN s_matnr. + SORT gt_keys BY material plant. + + LOOP AT gt_keys INTO gs_key. + CLEAR: gv_gr, gv_gi, gv_cnt, gs_out. + + SELECT SUM( quantity ) FROM {{p}}move INTO gv_gr + WHERE material = gs_key-material + AND plant = gs_key-plant + AND move_type = 'GR' + AND move_date >= gv_from + AND move_date <= gv_to. + + SELECT SUM( quantity ) FROM {{p}}move INTO gv_gi + WHERE material = gs_key-material + AND plant = gs_key-plant + AND move_type = 'GI' + AND move_date >= gv_from + AND move_date <= gv_to. + + SELECT COUNT(*) FROM {{p}}move INTO gv_cnt + WHERE material = gs_key-material + AND plant = gs_key-plant + AND move_date >= gv_from + AND move_date <= gv_to. + + gs_out-material = gs_key-material. + gs_out-plant = gs_key-plant. + gs_out-net_qty = gv_gr - gv_gi. + gs_out-move_count = gv_cnt. + APPEND gs_out TO gt_out. + ENDLOOP. + + TRY. + CALL METHOD cl_salv_table=>factory + IMPORTING + r_salv_table = go_alv + CHANGING + t_table = gt_out. + CALL METHOD go_alv->display. + CATCH cx_salv_msg INTO gx_msg. + MESSAGE gx_msg TYPE 'I' DISPLAY LIKE 'E'. + ENDTRY. diff --git a/tasks_gen/eval/G0016/spec.md b/tasks_gen/eval/G0016/spec.md new file mode 100644 index 0000000..50b30e2 --- /dev/null +++ b/tasks_gen/eval/G0016/spec.md @@ -0,0 +1,56 @@ +# 1. Goal +The program {{P}}STOCK_NET_OLD is a legacy report. It shows the net stock +quantity for each material and plant in a period. The report is slow and hard +to read. Write the program {{P}}STOCK_NET. The new program has the same +selection screen and the same result as the legacy report, but the code is +clean and the movements are read with one database access. + +# 2. Open questions +None. + +# 3. Context +- The table {{P}}MOVE exists in package $TMP and contains data. Fields: + - MOV_ID (INT4, key): the movement number. + - MATERIAL (CHAR 10): the material number. + - PLANT (CHAR 4): the plant. + - MOVE_DATE (DATS): the movement date. + - MOVE_TYPE (CHAR 2): 'GR' for a goods receipt, 'GI' for a goods issue. + - QUANTITY (DEC 15,3): the movement quantity. +- The program {{P}}STOCK_NET_OLD exists in package $TMP. It is legacy code. It +gives the correct result. +- The class {{P}}SEED_MOVE fills the table {{P}}MOVE with data. + +# 4. Contract +- Create the executable program {{P}}STOCK_NET 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_MATNR for the material number. +- Show the result with CL_SALV_TABLE. The output table has these columns with +these names: MATERIAL, PLANT, NET_QTY, MOVE_COUNT. + +# 5. Business rules +1. Include a movement only if MOVE_DATE is on or after P_FROM, MOVE_DATE is on +or before P_TO, and MATERIAL is in S_MATNR. +2. Group the included movements by MATERIAL and PLANT. +3. NET_QTY is the sum of QUANTITY of all included goods receipts in the group +minus the sum of QUANTITY of all included goods issues in the group. +4. MOVE_COUNT is the number of included movements in the group. +5. Show one line for each material and plant that has at least one included +movement. +6. Sort the lines by MATERIAL in ascending order and by PLANT in ascending +order. +7. The result of {{P}}STOCK_NET is the same as the result of +{{P}}STOCK_NET_OLD. + +# 6. Constraints +- Release target: 8.16. +- Coding standards: Clean ABAP. Methods have less than 40 statements. Read the +movements with one database access. Do not use obsolete statements. +- Out of scope: do not change the table {{P}}MOVE, the class {{P}}SEED_MOVE and +the program {{P}}STOCK_NET_OLD. + +# 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. diff --git a/tasks_gen/eval/G0016/task.json b/tasks_gen/eval/G0016/task.json new file mode 100644 index 0000000..d0b65be --- /dev/null +++ b/tasks_gen/eval/G0016/task.json @@ -0,0 +1,70 @@ +{ + "id": "G0016", + "category": "E", + "object_type": "PROG", + "difficulty": 2, + "release_target": "v816", + "expected_outcome": "implement", + "budget": { + "max_tool_calls": 40, + "max_activations": 12 + }, + "seed": [ + { + "type": "TABL", + "name": "{{P}}MOVE", + "file": "seed/move.tabl.asabap", + "description": "Stock movements" + }, + { + "type": "CLAS", + "name": "{{P}}SEED_MOVE", + "file": "seed/seed_move.clas.abap", + "description": "Stock movement seed data", + "run": true + }, + { + "type": "PROG", + "name": "{{P}}STOCK_NET_OLD", + "file": "seed/stock_net_old.prog.abap", + "description": "Legacy net stock report" + } + ], + "contract": [ + { + "type": "PROG", + "name": "{{P}}STOCK_NET", + "parameters": [ + "p_from", + "p_to", + "s_matnr" + ] + } + ], + "out_of_scope": [ + "{{P}}MOVE", + "{{P}}SEED_MOVE", + "{{P}}STOCK_NET_OLD" + ], + "hidden_tests": [ + { + "type": "CLAS", + "name": "{{P}}T15_HIDDEN", + "file": "hidden/t15_hidden.clas.abap", + "description": "T15 hidden tests" + } + ], + "reference": [ + { + "type": "PROG", + "name": "{{P}}STOCK_NET", + "file": "reference/stock_net.prog.abap", + "description": "Refactored net stock report" + } + ], + "craft_checks": [ + "db_operation_in_loop", + "method_length", + "obsolete_statements" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0017/generation.json b/tasks_gen/eval/G0017/generation.json new file mode 100644 index 0000000..32cc552 --- /dev/null +++ b/tasks_gen/eval/G0017/generation.json @@ -0,0 +1,29 @@ +{ + "id": "G0017", + "pool": "eval", + "object_type": "DDLS", + "category": "B", + "attempts": [ + { + "stage": "validate", + "oracle": null, + "null": null + }, + { + "stage": "validate", + "oracle": 0, + "null": 0 + }, + { + "stage": "validate", + "oracle": 98.5, + "null": 0 + }, + { + "stage": "validate", + "oracle": 100.0, + "null": 0 + } + ], + "accepted": true +} \ No newline at end of file diff --git a/tasks_gen/eval/G0017/hidden/t21_hidden.clas.abap b/tasks_gen/eval/G0017/hidden/t21_hidden.clas.abap new file mode 100644 index 0000000..84664d4 --- /dev/null +++ b/tasks_gen/eval/G0017/hidden/t21_hidden.clas.abap @@ -0,0 +1,86 @@ +CLASS {{p}}t21_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC + FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + CLASS-DATA go_env TYPE REF TO if_cds_test_environment. + CLASS-METHODS class_setup. + CLASS-METHODS class_teardown. + METHODS setup. + METHODS cost_of_project_one FOR TESTING. + METHODS hours_of_project_one FOR TESTING. + METHODS count_of_project_one FOR TESTING. + METHODS no_active_assignment FOR TESTING. + METHODS row_count FOR TESTING. + METHODS over_budget_flag FOR TESTING. + METHODS budget_not_exceeded FOR TESTING. + METHODS project_name_shown FOR TESTING. +ENDCLASS. + + +CLASS {{p}}t21_hidden IMPLEMENTATION. + METHOD class_setup. + go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_PROJ_EFFORT' ). + ENDMETHOD. + + METHOD class_teardown. + go_env->destroy( ). + ENDMETHOD. + + METHOD setup. + DATA lt_proj TYPE STANDARD TABLE OF {{p}}proj WITH EMPTY KEY. + DATA lt_asgn TYPE STANDARD TABLE OF {{p}}asgn WITH EMPTY KEY. + go_env->clear_doubles( ). + lt_proj = VALUE #( ( proj_id = 'P1' proj_name = 'ALPHA' budget = '500.00' ) + ( proj_id = 'P2' proj_name = 'BETA' budget = '100.00' ) + ( proj_id = 'P3' proj_name = 'GAMMA' budget = '1000.00' ) + ( proj_id = 'P4' proj_name = 'DELTA' budget = '50.00' ) ). + lt_asgn = VALUE #( ( proj_id = 'P1' emp_id = 'E1' work_hours = 10 rate = '50.00' status = 'A' ) + ( proj_id = 'P1' emp_id = 'E2' work_hours = 4 rate = '25.00' status = 'A' ) + ( proj_id = 'P1' emp_id = 'E3' work_hours = 8 rate = '100.00' status = 'I' ) + ( proj_id = 'P2' emp_id = 'E1' work_hours = 2 rate = '50.00' status = 'A' ) + ( proj_id = 'P4' emp_id = 'E2' work_hours = 5 rate = '10.00' status = 'I' ) ). + go_env->insert_test_data( lt_proj ). + go_env->insert_test_data( lt_asgn ). + ENDMETHOD. + + METHOD cost_of_project_one. + SELECT SINGLE total_cost FROM {{p}}i_proj_effort WHERE proj_id = 'P1' INTO @DATA(lv_cost). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '600.00' ) + act = CONV decfloat34( lv_cost ) ). + ENDMETHOD. + + METHOD hours_of_project_one. + SELECT SINGLE total_hours FROM {{p}}i_proj_effort WHERE proj_id = 'P1' INTO @DATA(lv_hours). + cl_abap_unit_assert=>assert_equals( exp = CONV i( lv_hours ) act = 14 ). + ENDMETHOD. + + METHOD count_of_project_one. + SELECT SINGLE assign_count FROM {{p}}i_proj_effort WHERE proj_id = 'P1' INTO @DATA(lv_count). + cl_abap_unit_assert=>assert_equals( exp = CONV i( lv_count ) act = 2 ). + ENDMETHOD. + + METHOD no_active_assignment. + SELECT SINGLE proj_name FROM {{p}}i_proj_effort WHERE proj_id = 'P4' INTO @DATA(lv_name). + cl_abap_unit_assert=>assert_equals( exp = 4 act = sy-subrc ). + cl_abap_unit_assert=>assert_initial( lv_name ). + ENDMETHOD. + + METHOD row_count. + SELECT COUNT(*) FROM {{p}}i_proj_effort INTO @DATA(lv_rows). + cl_abap_unit_assert=>assert_equals( exp = CONV i( lv_rows ) act = 2 ). + ENDMETHOD. + + METHOD over_budget_flag. + SELECT SINGLE over_budget FROM {{p}}i_proj_effort WHERE proj_id = 'P1' INTO @DATA(lv_flag). + cl_abap_unit_assert=>assert_equals( exp = 'X' act = lv_flag ). + ENDMETHOD. + + METHOD budget_not_exceeded. + SELECT SINGLE over_budget FROM {{p}}i_proj_effort WHERE proj_id = 'P2' INTO @DATA(lv_flag). + cl_abap_unit_assert=>assert_equals( exp = 'N' act = lv_flag ). + ENDMETHOD. + + METHOD project_name_shown. + SELECT SINGLE proj_name FROM {{p}}i_proj_effort WHERE proj_id = 'P2' INTO @DATA(lv_name). + cl_abap_unit_assert=>assert_equals( exp = 'BETA' act = lv_name ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0017/reference/i_proj_effort.ddls.asddls b/tasks_gen/eval/G0017/reference/i_proj_effort.ddls.asddls new file mode 100644 index 0000000..0904a9b --- /dev/null +++ b/tasks_gen/eval/G0017/reference/i_proj_effort.ddls.asddls @@ -0,0 +1,22 @@ +@AccessControl.authorizationCheck: #NOT_REQUIRED +@EndUserText.label: 'Project effort' +define view entity {{P}}I_PROJ_EFFORT + as select from {{p}}proj as proj + inner join {{p}}asgn as asgn on asgn.proj_id = proj.proj_id +{ + key proj.proj_id as proj_id, + proj.proj_name as proj_name, + count( * ) as assign_count, + sum( asgn.work_hours ) as total_hours, + sum( cast( asgn.work_hours as abap.dec( 15, 2 ) ) * asgn.rate ) as total_cost, + case + when sum( cast( asgn.work_hours as abap.dec( 15, 2 ) ) * asgn.rate ) > proj.budget + then 'X' + else 'N' + end as over_budget +} +where asgn.status = 'A' +group by + proj.proj_id, + proj.proj_name, + proj.budget diff --git a/tasks_gen/eval/G0017/reference/t21_test.clas.abap b/tasks_gen/eval/G0017/reference/t21_test.clas.abap new file mode 100644 index 0000000..c4b2048 --- /dev/null +++ b/tasks_gen/eval/G0017/reference/t21_test.clas.abap @@ -0,0 +1,53 @@ +CLASS {{p}}t21_test DEFINITION PUBLIC FINAL CREATE PUBLIC + FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + CLASS-DATA go_env TYPE REF TO if_cds_test_environment. + CLASS-METHODS class_setup. + CLASS-METHODS class_teardown. + METHODS setup. + METHODS effort_of_one_project FOR TESTING. + METHODS no_active_assignment FOR TESTING. +ENDCLASS. + + +CLASS {{p}}t21_test IMPLEMENTATION. + METHOD class_setup. + go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_PROJ_EFFORT' ). + ENDMETHOD. + + METHOD class_teardown. + go_env->destroy( ). + ENDMETHOD. + + METHOD setup. + DATA lt_proj TYPE STANDARD TABLE OF {{p}}proj WITH EMPTY KEY. + DATA lt_asgn TYPE STANDARD TABLE OF {{p}}asgn WITH EMPTY KEY. + go_env->clear_doubles( ). + lt_proj = VALUE #( ( proj_id = 'A' proj_name = 'ONE' budget = '100.00' ) + ( proj_id = 'B' proj_name = 'TWO' budget = '10.00' ) ). + lt_asgn = VALUE #( ( proj_id = 'A' emp_id = 'E1' work_hours = 2 rate = '10.00' status = 'A' ) + ( proj_id = 'A' emp_id = 'E2' work_hours = 1 rate = '5.00' status = 'A' ) + ( proj_id = 'A' emp_id = 'E3' work_hours = 9 rate = '9.00' status = 'I' ) + ( proj_id = 'B' emp_id = 'E1' work_hours = 3 rate = '1.00' status = 'I' ) ). + go_env->insert_test_data( lt_proj ). + go_env->insert_test_data( lt_asgn ). + ENDMETHOD. + + METHOD effort_of_one_project. + SELECT SINGLE assign_count, total_hours, total_cost, over_budget + FROM {{p}}i_proj_effort + WHERE proj_id = 'A' + INTO @DATA(ls_effort). + cl_abap_unit_assert=>assert_equals( exp = CONV i( ls_effort-assign_count ) act = 2 ). + cl_abap_unit_assert=>assert_equals( exp = CONV i( ls_effort-total_hours ) act = 3 ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '25.00' ) + act = CONV decfloat34( ls_effort-total_cost ) ). + cl_abap_unit_assert=>assert_equals( exp = 'N' act = ls_effort-over_budget ). + ENDMETHOD. + + METHOD no_active_assignment. + SELECT SINGLE proj_id FROM {{p}}i_proj_effort WHERE proj_id = 'B' INTO @DATA(lv_proj_id). + cl_abap_unit_assert=>assert_equals( exp = 4 act = sy-subrc ). + cl_abap_unit_assert=>assert_initial( lv_proj_id ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0017/seed/asgn.tabl.asabap b/tasks_gen/eval/G0017/seed/asgn.tabl.asabap new file mode 100644 index 0000000..b2ae3ef --- /dev/null +++ b/tasks_gen/eval/G0017/seed/asgn.tabl.asabap @@ -0,0 +1,13 @@ +@EndUserText.label : 'Project assignment' +@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE +@AbapCatalog.tableCategory : #TRANSPARENT +@AbapCatalog.deliveryClass : #A +@AbapCatalog.dataMaintenance : #RESTRICTED +define table {{p}}asgn { + key client : abap.clnt not null; + key proj_id : abap.char(10) not null; + key emp_id : abap.char(10) not null; + work_hours : abap.int4; + rate : abap.dec(15,2); + status : abap.char(1); +} diff --git a/tasks_gen/eval/G0017/seed/proj.tabl.asabap b/tasks_gen/eval/G0017/seed/proj.tabl.asabap new file mode 100644 index 0000000..df44ee3 --- /dev/null +++ b/tasks_gen/eval/G0017/seed/proj.tabl.asabap @@ -0,0 +1,11 @@ +@EndUserText.label : 'Project' +@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE +@AbapCatalog.tableCategory : #TRANSPARENT +@AbapCatalog.deliveryClass : #A +@AbapCatalog.dataMaintenance : #RESTRICTED +define table {{p}}proj { + key client : abap.clnt not null; + key proj_id : abap.char(10) not null; + proj_name : abap.char(30); + budget : abap.dec(15,2); +} diff --git a/tasks_gen/eval/G0017/spec.md b/tasks_gen/eval/G0017/spec.md new file mode 100644 index 0000000..51d5779 --- /dev/null +++ b/tasks_gen/eval/G0017/spec.md @@ -0,0 +1,42 @@ +# 1. Goal +Show the effort of each project. A report and an OData service read the effort, +so the calculation must be in the database, in a CDS view. + +# 2. Open questions +None. + +# 3. Context +- Table {{P}}PROJ (project), package $TMP. Key: PROJ_ID (CHAR 10). + Fields: PROJ_NAME (CHAR 30), BUDGET (DEC 15,2). +- Table {{P}}ASGN (project assignment), package $TMP. Key: PROJ_ID (CHAR 10), + EMP_ID (CHAR 10). Fields: WORK_HOURS (INT4, the hours of the assignment), + RATE (DEC 15,2, cost of one hour), STATUS (CHAR 1). + +# 4. Contract +- Create the CDS view entity {{P}}I_PROJ_EFFORT in package $TMP. +- Elements, with these names: PROJ_ID (key), PROJ_NAME, ASSIGN_COUNT, + TOTAL_HOURS, TOTAL_COST, OVER_BUDGET. +- No authorization check (#NOT_REQUIRED). + +# 5. Business rules +1. Show one row for each project that has at least one active assignment. + An assignment is active when its STATUS is 'A'. An assignment with + another status is not active. A project without active assignments does + not appear in the view. +2. ASSIGN_COUNT is the number of active assignments of the project. +3. TOTAL_HOURS is the sum of WORK_HOURS of the active assignments of the + project. +4. TOTAL_COST is the sum of WORK_HOURS multiplied by RATE of the active + assignments of the project, with 2 decimals. +5. OVER_BUDGET is 'X' when TOTAL_COST is greater than the BUDGET of the + project. Otherwise OVER_BUDGET is 'N'. + +# 6. Constraints +- Release target: 7.58. +- Coding standards: Clean ABAP. +- Out of scope: do not change {{P}}PROJ and {{P}}ASGN. + +# 7. Acceptance +- The view is active and has no syntax error. +- The hidden tests pass. +- Write ABAP Unit tests with CL_CDS_TEST_ENVIRONMENT in a global test class. diff --git a/tasks_gen/eval/G0017/task.json b/tasks_gen/eval/G0017/task.json new file mode 100644 index 0000000..44687eb --- /dev/null +++ b/tasks_gen/eval/G0017/task.json @@ -0,0 +1,67 @@ +{ + "id": "G0017", + "category": "B", + "object_type": "DDLS", + "difficulty": 2, + "release_target": "v758", + "expected_outcome": "implement", + "budget": { + "max_tool_calls": 40, + "max_activations": 10 + }, + "seed": [ + { + "type": "TABL", + "name": "{{P}}PROJ", + "file": "seed/proj.tabl.asabap", + "description": "Project" + }, + { + "type": "TABL", + "name": "{{P}}ASGN", + "file": "seed/asgn.tabl.asabap", + "description": "Project assignment" + } + ], + "contract": [ + { + "type": "DDLS", + "name": "{{P}}I_PROJ_EFFORT", + "fields": [ + "PROJ_ID", + "PROJ_NAME", + "ASSIGN_COUNT", + "TOTAL_HOURS", + "TOTAL_COST", + "OVER_BUDGET" + ] + } + ], + "out_of_scope": [ + "{{P}}PROJ", + "{{P}}ASGN" + ], + "hidden_tests": [ + { + "type": "CLAS", + "name": "{{P}}T21_HIDDEN", + "file": "hidden/t21_hidden.clas.abap", + "description": "T21 hidden tests" + } + ], + "reference": [ + { + "type": "DDLS", + "name": "{{P}}I_PROJ_EFFORT", + "file": "reference/i_proj_effort.ddls.asddls", + "description": "Project effort" + }, + { + "type": "CLAS", + "name": "{{P}}T21_TEST", + "file": "reference/t21_test.clas.abap", + "description": "T21 own tests" + } + ], + "craft_checks": [] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0018/generation.json b/tasks_gen/eval/G0018/generation.json new file mode 100644 index 0000000..329a6ac --- /dev/null +++ b/tasks_gen/eval/G0018/generation.json @@ -0,0 +1,24 @@ +{ + "id": "G0018", + "pool": "eval", + "object_type": "DDLS", + "category": "B", + "attempts": [ + { + "stage": "validate", + "oracle": 0, + "null": 0 + }, + { + "stage": "validate", + "oracle": 0, + "null": 0 + }, + { + "stage": "validate", + "oracle": 100.0, + "null": 0 + } + ], + "accepted": true +} \ No newline at end of file diff --git a/tasks_gen/eval/G0018/hidden/t20_hidden.clas.abap b/tasks_gen/eval/G0018/hidden/t20_hidden.clas.abap new file mode 100644 index 0000000..f3f5749 --- /dev/null +++ b/tasks_gen/eval/G0018/hidden/t20_hidden.clas.abap @@ -0,0 +1,79 @@ +CLASS {{p}}t20_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC + FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + CLASS-DATA go_env TYPE REF TO if_cds_test_environment. + CLASS-METHODS class_setup. + CLASS-METHODS class_teardown. + METHODS setup. + METHODS association_path FOR TESTING. + METHODS closed_order_hidden FOR TESTING. + METHODS customer_data_shown FOR TESTING. + METHODS open_orders_only FOR TESTING. + METHODS order_key_data FOR TESTING. + METHODS order_without_customer FOR TESTING. +ENDCLASS. + + +CLASS {{p}}t20_hidden IMPLEMENTATION. + METHOD class_setup. + go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_ORDER' ). + ENDMETHOD. + + METHOD class_teardown. + go_env->destroy( ). + ENDMETHOD. + + METHOD setup. + DATA lt_cust TYPE STANDARD TABLE OF {{p}}cust WITH EMPTY KEY. + DATA lt_ord TYPE STANDARD TABLE OF {{p}}ord WITH EMPTY KEY. + go_env->clear_doubles( ). + lt_cust = VALUE #( + ( cust_id = 'C001' cust_name = 'Alpha GmbH' country = 'DE' ) + ( cust_id = 'C002' cust_name = 'Beta Ltd' country = 'GB' ) ). + lt_ord = VALUE #( + ( order_id = 'O100' cust_id = 'C001' order_date = '20250110' status = 'O' ) + ( order_id = 'O101' cust_id = 'C002' order_date = '20250111' status = 'O' ) + ( order_id = 'O102' cust_id = 'C999' order_date = '20250112' status = 'O' ) + ( order_id = 'O103' cust_id = 'C001' order_date = '20250113' status = 'C' ) ). + go_env->insert_test_data( lt_cust ). + go_env->insert_test_data( lt_ord ). + ENDMETHOD. + + METHOD open_orders_only. + SELECT COUNT(*) FROM {{p}}i_order INTO @DATA(lv_count). + cl_abap_unit_assert=>assert_equals( exp = 3 act = lv_count ). + ENDMETHOD. + + METHOD closed_order_hidden. + SELECT SINGLE order_id FROM {{p}}i_order + WHERE order_id = 'O103' INTO @DATA(lv_id). + cl_abap_unit_assert=>assert_initial( act = lv_id ). + ENDMETHOD. + + METHOD order_key_data. + SELECT SINGLE cust_id, order_date FROM {{p}}i_order + WHERE order_id = 'O100' INTO @DATA(ls_ord). + cl_abap_unit_assert=>assert_equals( exp = 'C001' act = ls_ord-cust_id ). + cl_abap_unit_assert=>assert_equals( exp = CONV d( '20250110' ) act = ls_ord-order_date ). + ENDMETHOD. + + METHOD customer_data_shown. + SELECT SINGLE customer_name, country FROM {{p}}i_order + WHERE order_id = 'O101' INTO @DATA(ls_ord). + cl_abap_unit_assert=>assert_equals( exp = 'Beta Ltd' act = ls_ord-customer_name ). + cl_abap_unit_assert=>assert_equals( exp = 'GB' act = ls_ord-country ). + ENDMETHOD. + + METHOD order_without_customer. + SELECT SINGLE customer_name, country FROM {{p}}i_order + WHERE order_id = 'O102' INTO @DATA(ls_ord). + cl_abap_unit_assert=>assert_initial( act = ls_ord-customer_name ). + cl_abap_unit_assert=>assert_initial( act = ls_ord-country ). + ENDMETHOD. + + METHOD association_path. + SELECT SINGLE \_customer-cust_name FROM {{p}}i_order + WHERE order_id = 'O100' INTO @DATA(lv_name). + cl_abap_unit_assert=>assert_equals( exp = 'Alpha GmbH' act = lv_name ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0018/reference/i_customer.ddls.asddls b/tasks_gen/eval/G0018/reference/i_customer.ddls.asddls new file mode 100644 index 0000000..750a9e8 --- /dev/null +++ b/tasks_gen/eval/G0018/reference/i_customer.ddls.asddls @@ -0,0 +1,9 @@ +@AccessControl.authorizationCheck: #NOT_REQUIRED +@EndUserText.label: 'Customer master' +define view entity {{P}}I_CUSTOMER + as select from {{p}}cust +{ + key cust_id, + cust_name, + country +} diff --git a/tasks_gen/eval/G0018/reference/i_order.ddls.asddls b/tasks_gen/eval/G0018/reference/i_order.ddls.asddls new file mode 100644 index 0000000..fa253bc --- /dev/null +++ b/tasks_gen/eval/G0018/reference/i_order.ddls.asddls @@ -0,0 +1,15 @@ +@AccessControl.authorizationCheck: #NOT_REQUIRED +@EndUserText.label: 'Open orders with customer data' +define view entity {{P}}I_ORDER + as select from {{p}}ord as ord + association [0..1] to {{p}}cust as _Customer + on $projection.cust_id = _Customer.cust_id +{ + key ord.order_id, + ord.cust_id, + ord.order_date, + _Customer.cust_name as customer_name, + _Customer.country as country, + _Customer +} +where ord.status = 'O' diff --git a/tasks_gen/eval/G0018/reference/t20_test.clas.abap b/tasks_gen/eval/G0018/reference/t20_test.clas.abap new file mode 100644 index 0000000..dccbd74 --- /dev/null +++ b/tasks_gen/eval/G0018/reference/t20_test.clas.abap @@ -0,0 +1,48 @@ +CLASS {{p}}t20_test DEFINITION PUBLIC FINAL CREATE PUBLIC + FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + CLASS-DATA go_env TYPE REF TO if_cds_test_environment. + CLASS-METHODS class_setup. + CLASS-METHODS class_teardown. + METHODS setup. + METHODS order_with_customer FOR TESTING. + METHODS order_without_customer FOR TESTING. +ENDCLASS. + + +CLASS {{p}}t20_test IMPLEMENTATION. + METHOD class_setup. + go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_ORDER' ). + ENDMETHOD. + + METHOD class_teardown. + go_env->destroy( ). + ENDMETHOD. + + METHOD setup. + DATA lt_cust TYPE STANDARD TABLE OF {{p}}cust WITH EMPTY KEY. + DATA lt_ord TYPE STANDARD TABLE OF {{p}}ord WITH EMPTY KEY. + go_env->clear_doubles( ). + lt_cust = VALUE #( + ( cust_id = 'C001' cust_name = 'Alpha' country = 'DE' ) ). + lt_ord = VALUE #( + ( order_id = 'O1' cust_id = 'C001' order_date = '20250101' status = 'O' ) + ( order_id = 'O2' cust_id = 'C999' order_date = '20250102' status = 'O' ) + ( order_id = 'O3' cust_id = 'C001' order_date = '20250103' status = 'C' ) ). + go_env->insert_test_data( lt_cust ). + go_env->insert_test_data( lt_ord ). + ENDMETHOD. + + METHOD order_with_customer. + SELECT SINGLE customer_name, country FROM {{p}}i_order + WHERE order_id = 'O1' INTO @DATA(ls_ord). + cl_abap_unit_assert=>assert_equals( exp = 'Alpha' act = ls_ord-customer_name ). + cl_abap_unit_assert=>assert_equals( exp = 'DE' act = ls_ord-country ). + ENDMETHOD. + + METHOD order_without_customer. + SELECT SINGLE customer_name FROM {{p}}i_order + WHERE order_id = 'O2' INTO @DATA(lv_name). + cl_abap_unit_assert=>assert_initial( act = lv_name ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0018/seed/cust.tabl.asabap b/tasks_gen/eval/G0018/seed/cust.tabl.asabap new file mode 100644 index 0000000..01be425 --- /dev/null +++ b/tasks_gen/eval/G0018/seed/cust.tabl.asabap @@ -0,0 +1,11 @@ +@EndUserText.label : 'Customer master' +@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE +@AbapCatalog.tableCategory : #TRANSPARENT +@AbapCatalog.deliveryClass : #A +@AbapCatalog.dataMaintenance : #RESTRICTED +define table {{p}}cust { + key client : abap.clnt not null; + key cust_id : abap.char(10) not null; + cust_name : abap.char(40); + country : abap.char(3); +} diff --git a/tasks_gen/eval/G0018/seed/ord.tabl.asabap b/tasks_gen/eval/G0018/seed/ord.tabl.asabap new file mode 100644 index 0000000..b41353d --- /dev/null +++ b/tasks_gen/eval/G0018/seed/ord.tabl.asabap @@ -0,0 +1,12 @@ +@EndUserText.label : 'Order header' +@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE +@AbapCatalog.tableCategory : #TRANSPARENT +@AbapCatalog.deliveryClass : #A +@AbapCatalog.dataMaintenance : #RESTRICTED +define table {{p}}ord { + key client : abap.clnt not null; + key order_id : abap.char(10) not null; + cust_id : abap.char(10); + order_date : abap.dats; + status : abap.char(1); +} diff --git a/tasks_gen/eval/G0018/seed/seed_data.clas.abap b/tasks_gen/eval/G0018/seed/seed_data.clas.abap new file mode 100644 index 0000000..f52d9a4 --- /dev/null +++ b/tasks_gen/eval/G0018/seed/seed_data.clas.abap @@ -0,0 +1,22 @@ +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}}ord. + DELETE FROM {{p}}cust. + INSERT {{p}}cust FROM TABLE @( VALUE #( + ( client = sy-mandt cust_id = 'C001' cust_name = 'Alpha GmbH' country = 'DE' ) + ( client = sy-mandt cust_id = 'C002' cust_name = 'Beta Ltd' country = 'GB' ) + ( client = sy-mandt cust_id = 'C003' cust_name = 'Gamma SA' country = 'FR' ) ) ). + INSERT {{p}}ord FROM TABLE @( VALUE #( + ( client = sy-mandt order_id = 'O100' cust_id = 'C001' order_date = '20250110' status = 'O' ) + ( client = sy-mandt order_id = 'O101' cust_id = 'C002' order_date = '20250111' status = 'O' ) + ( client = sy-mandt order_id = 'O102' cust_id = 'C999' order_date = '20250112' status = 'O' ) + ( client = sy-mandt order_id = 'O103' cust_id = 'C003' order_date = '20250113' status = 'C' ) ) ). + out->write( 'Seed data written' ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0018/spec.md b/tasks_gen/eval/G0018/spec.md new file mode 100644 index 0000000..3c1a67a --- /dev/null +++ b/tasks_gen/eval/G0018/spec.md @@ -0,0 +1,46 @@ +# 1. Goal +Show every open order together with the name and country of its customer. A +list report and an OData service read this data, so the data must be prepared +in the database, in a CDS view entity. + +# 2. Open questions +None. + +# 3. Context +- Table {{P}}CUST (customer master), package $TMP. + Key: CUST_ID (CHAR 10). Further fields: CUST_NAME (CHAR 40), COUNTRY (CHAR 3). +- Table {{P}}ORD (order header), package $TMP. + Key: ORDER_ID (CHAR 10). Further fields: CUST_ID (CHAR 10), ORDER_DATE (DATS), + STATUS (CHAR 1). + STATUS is 'O' for an open order and 'C' for a closed order. + +# 4. Contract +- Create the CDS view entity {{P}}I_ORDER in package $TMP. +- Elements, with these names: ORDER_ID (key), CUST_ID, ORDER_DATE, + CUSTOMER_NAME, COUNTRY. +- Define an association named _Customer in {{P}}I_ORDER. The association has + cardinality [0..1] and points to the customer master table {{P}}CUST. The + association condition compares the CUST_ID of the order with the CUST_ID of + the customer. +- Expose the association _Customer in the element list of {{P}}I_ORDER and read + CUSTOMER_NAME and COUNTRY with path expressions through this association. +- The view entity has no authorization check (#NOT_REQUIRED). + +# 5. Business rules +1. {{P}}I_ORDER shows one row for each order with STATUS 'O'. An order with + STATUS 'C' is not shown. +2. CUSTOMER_NAME is the CUST_NAME and COUNTRY is the COUNTRY of the customer + whose CUST_ID is the CUST_ID of the order. +3. If an order has a CUST_ID for which no customer exists, the order is still + shown. CUSTOMER_NAME and COUNTRY are then empty. +4. ORDER_ID, CUST_ID and ORDER_DATE are the values of the order. + +# 6. Constraints +- Release target: 7.58. +- Coding standards: Clean ABAP. +- Out of scope: do not change {{P}}CUST and {{P}}ORD. + +# 7. Acceptance +- The view entity is active and has no syntax error. +- The hidden tests pass. +- Write ABAP Unit tests with CL_CDS_TEST_ENVIRONMENT in a global test class. diff --git a/tasks_gen/eval/G0018/task.json b/tasks_gen/eval/G0018/task.json new file mode 100644 index 0000000..cff26d4 --- /dev/null +++ b/tasks_gen/eval/G0018/task.json @@ -0,0 +1,73 @@ +{ + "id": "G0018", + "category": "B", + "object_type": "DDLS", + "difficulty": 2, + "release_target": "v758", + "expected_outcome": "implement", + "budget": { + "max_tool_calls": 40, + "max_activations": 10 + }, + "seed": [ + { + "type": "TABL", + "name": "{{P}}CUST", + "file": "seed/cust.tabl.asabap", + "description": "Customer master" + }, + { + "type": "TABL", + "name": "{{P}}ORD", + "file": "seed/ord.tabl.asabap", + "description": "Order header" + }, + { + "type": "CLAS", + "name": "{{P}}SEED_DATA", + "file": "seed/seed_data.clas.abap", + "description": "Seed data for {{P}}CUST and {{P}}ORD", + "run": true + } + ], + "contract": [ + { + "type": "DDLS", + "name": "{{P}}I_ORDER", + "fields": [ + "ORDER_ID", + "CUST_ID", + "ORDER_DATE", + "CUSTOMER_NAME", + "COUNTRY" + ] + } + ], + "out_of_scope": [ + "{{P}}CUST", + "{{P}}ORD" + ], + "hidden_tests": [ + { + "type": "CLAS", + "name": "{{P}}T20_HIDDEN", + "file": "hidden/t20_hidden.clas.abap", + "description": "T20 hidden tests" + } + ], + "reference": [ + { + "type": "DDLS", + "name": "{{P}}I_ORDER", + "file": "reference/i_order.ddls.asddls", + "description": "Open orders with customer data" + }, + { + "type": "CLAS", + "name": "{{P}}T20_TEST", + "file": "reference/t20_test.clas.abap", + "description": "T20 own tests" + } + ], + "craft_checks": [] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0019/generation.json b/tasks_gen/eval/G0019/generation.json new file mode 100644 index 0000000..f1728a0 --- /dev/null +++ b/tasks_gen/eval/G0019/generation.json @@ -0,0 +1,19 @@ +{ + "id": "G0019", + "pool": "eval", + "object_type": "DDLS", + "category": "B", + "attempts": [ + { + "stage": "validate", + "oracle": 0, + "null": 0 + }, + { + "stage": "validate", + "oracle": 100.0, + "null": 0 + } + ], + "accepted": true +} \ No newline at end of file diff --git a/tasks_gen/eval/G0019/hidden/t20_hidden.clas.abap b/tasks_gen/eval/G0019/hidden/t20_hidden.clas.abap new file mode 100644 index 0000000..8bc0ec3 --- /dev/null +++ b/tasks_gen/eval/G0019/hidden/t20_hidden.clas.abap @@ -0,0 +1,109 @@ +CLASS {{p}}t20_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC + FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + CLASS-DATA go_env TYPE REF TO if_cds_test_environment. + CLASS-METHODS class_setup. + CLASS-METHODS class_teardown. + METHODS setup. + METHODS small_count FOR TESTING. + METHODS medium_count FOR TESTING. + METHODS large_count FOR TESTING. + METHODS item_count FOR TESTING. + METHODS total_amount FOR TESTING. + METHODS cancelled_items_ignored FOR TESTING. + METHODS regions_in_result FOR TESTING. + METHODS class_boundaries FOR TESTING. +ENDCLASS. + + +CLASS {{p}}t20_hidden IMPLEMENTATION. + METHOD class_setup. + go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_SALES_CLASS' ). + ENDMETHOD. + + METHOD class_teardown. + go_env->destroy( ). + ENDMETHOD. + + METHOD setup. + DATA lt_sale TYPE STANDARD TABLE OF {{p}}sale WITH EMPTY KEY. + go_env->clear_doubles( ). + lt_sale = VALUE #( + ( sale_id = 'S1' item_no = 1 region = 'AA' amount = '0.01' status = 'A' ) + ( sale_id = 'S1' item_no = 2 region = 'AA' amount = '99.99' status = 'A' ) + ( sale_id = 'S1' item_no = 3 region = 'AA' amount = '100.00' status = 'A' ) + ( sale_id = 'S1' item_no = 4 region = 'AA' amount = '999.99' status = 'A' ) + ( sale_id = 'S1' item_no = 5 region = 'AA' amount = '1000.00' status = 'A' ) + ( sale_id = 'S2' item_no = 1 region = 'BB' amount = '10.00' status = 'A' ) + ( sale_id = 'S2' item_no = 2 region = 'BB' amount = '20.00' status = 'X' ) + ( sale_id = 'S3' item_no = 1 region = 'CC' amount = '500.00' status = 'X' ) ). + go_env->insert_test_data( lt_sale ). + ENDMETHOD. + + METHOD small_count. + SELECT SINGLE small_count FROM {{p}}i_sales_class WHERE region = 'AA' INTO @DATA(lv_aa). + SELECT SINGLE small_count FROM {{p}}i_sales_class WHERE region = 'BB' INTO @DATA(lv_bb). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 2 ) act = CONV int8( lv_aa ) ). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 1 ) act = CONV int8( lv_bb ) ). + ENDMETHOD. + + METHOD medium_count. + SELECT SINGLE medium_count FROM {{p}}i_sales_class WHERE region = 'AA' INTO @DATA(lv_aa). + SELECT SINGLE medium_count FROM {{p}}i_sales_class WHERE region = 'BB' INTO @DATA(lv_bb). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 2 ) act = CONV int8( lv_aa ) ). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 0 ) act = CONV int8( lv_bb ) ). + ENDMETHOD. + + METHOD large_count. + SELECT SINGLE large_count FROM {{p}}i_sales_class WHERE region = 'AA' INTO @DATA(lv_aa). + SELECT SINGLE large_count FROM {{p}}i_sales_class WHERE region = 'BB' INTO @DATA(lv_bb). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 1 ) act = CONV int8( lv_aa ) ). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 0 ) act = CONV int8( lv_bb ) ). + ENDMETHOD. + + METHOD item_count. + SELECT SINGLE item_count FROM {{p}}i_sales_class WHERE region = 'AA' INTO @DATA(lv_aa). + SELECT SINGLE item_count FROM {{p}}i_sales_class WHERE region = 'BB' INTO @DATA(lv_bb). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 5 ) act = CONV int8( lv_aa ) ). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 1 ) act = CONV int8( lv_bb ) ). + ENDMETHOD. + + METHOD total_amount. + SELECT SINGLE total_amount FROM {{p}}i_sales_class WHERE region = 'AA' INTO @DATA(lv_aa). + SELECT SINGLE total_amount FROM {{p}}i_sales_class WHERE region = 'BB' INTO @DATA(lv_bb). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '2199.99' ) + act = CONV decfloat34( lv_aa ) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '10.00' ) + act = CONV decfloat34( lv_bb ) ). + ENDMETHOD. + + METHOD cancelled_items_ignored. + SELECT SINGLE item_count, total_amount FROM {{p}}i_sales_class + WHERE region = 'BB' INTO (@DATA(lv_count), @DATA(lv_total)). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 1 ) act = CONV int8( lv_count ) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '10.00' ) + act = CONV decfloat34( lv_total ) ). + ENDMETHOD. + + METHOD regions_in_result. + SELECT COUNT(*) FROM {{p}}i_sales_class INTO @DATA(lv_rows). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 2 ) act = CONV int8( lv_rows ) ). + SELECT SINGLE region FROM {{p}}i_sales_class WHERE region = 'CC' INTO @DATA(lv_region). + cl_abap_unit_assert=>assert_initial( act = lv_region ). + ENDMETHOD. + + METHOD class_boundaries. + DATA lt_sale TYPE STANDARD TABLE OF {{p}}sale WITH EMPTY KEY. + go_env->clear_doubles( ). + lt_sale = VALUE #( + ( sale_id = 'B1' item_no = 1 region = 'AA' amount = '99.99' status = 'A' ) + ( sale_id = 'B1' item_no = 2 region = 'AA' amount = '100.00' status = 'A' ) + ( sale_id = 'B1' item_no = 3 region = 'AA' amount = '1000.00' status = 'A' ) ). + go_env->insert_test_data( lt_sale ). + SELECT SINGLE small_count, medium_count, large_count FROM {{p}}i_sales_class + WHERE region = 'AA' INTO (@DATA(lv_small), @DATA(lv_medium), @DATA(lv_large)). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 1 ) act = CONV int8( lv_small ) ). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 1 ) act = CONV int8( lv_medium ) ). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 1 ) act = CONV int8( lv_large ) ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0019/reference/i_sales_class.ddls.asddls b/tasks_gen/eval/G0019/reference/i_sales_class.ddls.asddls new file mode 100644 index 0000000..6b3e133 --- /dev/null +++ b/tasks_gen/eval/G0019/reference/i_sales_class.ddls.asddls @@ -0,0 +1,14 @@ +@AccessControl.authorizationCheck: #NOT_REQUIRED +@EndUserText.label: 'Sales items by region and size class' +define view entity {{P}}I_SALES_CLASS + as select from {{p}}sale +{ + key region as region, + count( * ) as item_count, + sum( case when amount < 100 then 1 else 0 end ) as small_count, + sum( case when amount >= 100 and amount < 1000 then 1 else 0 end ) as medium_count, + sum( case when amount >= 1000 then 1 else 0 end ) as large_count, + sum( amount ) as total_amount +} +where status <> 'X' +group by region diff --git a/tasks_gen/eval/G0019/reference/t20_test.clas.abap b/tasks_gen/eval/G0019/reference/t20_test.clas.abap new file mode 100644 index 0000000..300d749 --- /dev/null +++ b/tasks_gen/eval/G0019/reference/t20_test.clas.abap @@ -0,0 +1,50 @@ +CLASS {{p}}t20_test DEFINITION PUBLIC FINAL CREATE PUBLIC + FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + CLASS-DATA go_env TYPE REF TO if_cds_test_environment. + CLASS-METHODS class_setup. + CLASS-METHODS class_teardown. + METHODS setup. + METHODS class_counts FOR TESTING. + METHODS cancelled_region FOR TESTING. +ENDCLASS. + + +CLASS {{p}}t20_test IMPLEMENTATION. + METHOD class_setup. + go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_SALES_CLASS' ). + ENDMETHOD. + + METHOD class_teardown. + go_env->destroy( ). + ENDMETHOD. + + METHOD setup. + DATA lt_sale TYPE STANDARD TABLE OF {{p}}sale WITH EMPTY KEY. + go_env->clear_doubles( ). + lt_sale = VALUE #( + ( sale_id = 'A1' item_no = 1 region = 'AA' amount = '50.00' status = 'A' ) + ( sale_id = 'A1' item_no = 2 region = 'AA' amount = '100.00' status = 'A' ) + ( sale_id = 'A1' item_no = 3 region = 'AA' amount = '1000.00' status = 'A' ) + ( sale_id = 'A2' item_no = 1 region = 'BB' amount = '25.00' status = 'X' ) ). + go_env->insert_test_data( lt_sale ). + ENDMETHOD. + + METHOD class_counts. + SELECT SINGLE item_count, small_count, medium_count, large_count, total_amount + FROM {{p}}i_sales_class + WHERE region = 'AA' + INTO @DATA(ls_row). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 3 ) act = CONV int8( ls_row-item_count ) ). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 1 ) act = CONV int8( ls_row-small_count ) ). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 1 ) act = CONV int8( ls_row-medium_count ) ). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 1 ) act = CONV int8( ls_row-large_count ) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '1150.00' ) + act = CONV decfloat34( ls_row-total_amount ) ). + ENDMETHOD. + + METHOD cancelled_region. + SELECT COUNT(*) FROM {{p}}i_sales_class WHERE region = 'BB' INTO @DATA(lv_rows). + cl_abap_unit_assert=>assert_equals( exp = CONV int8( 0 ) act = CONV int8( lv_rows ) ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0019/seed/sale.tabl.asabap b/tasks_gen/eval/G0019/seed/sale.tabl.asabap new file mode 100644 index 0000000..16298b4 --- /dev/null +++ b/tasks_gen/eval/G0019/seed/sale.tabl.asabap @@ -0,0 +1,13 @@ +@EndUserText.label : 'Sales item' +@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE +@AbapCatalog.tableCategory : #TRANSPARENT +@AbapCatalog.deliveryClass : #A +@AbapCatalog.dataMaintenance : #RESTRICTED +define table {{p}}sale { + key client : abap.clnt not null; + key sale_id : abap.char(10) not null; + key item_no : abap.int4 not null; + region : abap.char(2); + amount : abap.dec(15,2); + status : abap.char(1) not null; +} diff --git a/tasks_gen/eval/G0019/spec.md b/tasks_gen/eval/G0019/spec.md new file mode 100644 index 0000000..ffe44ab --- /dev/null +++ b/tasks_gen/eval/G0019/spec.md @@ -0,0 +1,44 @@ +# 1. Goal +Show how many sales items and how much sales value each region has, split into +three size classes. A report reads the result. The calculation must run in the +database, in a CDS view. + +# 2. Open questions +None. + +# 3. Context +- Table {{P}}SALE (sales item), package $TMP. + Key: SALE_ID (CHAR 10), ITEM_NO (INT4). + Fields: REGION (CHAR 2), AMOUNT (DEC 15,2), STATUS (CHAR 1). +- STATUS 'X' means that the item is cancelled. + +# 4. Contract +- Create the CDS view entity {{P}}I_SALES_CLASS in package $TMP. +- Elements, with these names: REGION (key), ITEM_COUNT, SMALL_COUNT, + MEDIUM_COUNT, LARGE_COUNT, TOTAL_AMOUNT. +- No authorization check (#NOT_REQUIRED). + +# 5. Business rules +1. Ignore every item with STATUS 'X'. Do not count it and do not add its + amount. +2. Show one row for each region that has at least one item that is not + cancelled. Do not show a region without such an item. +3. An item is small if its amount is less than 100.00. +4. An item is medium if its amount is 100.00 or more and less than 1000.00. +5. An item is large if its amount is 1000.00 or more. +6. ITEM_COUNT is the number of items of the region that are not cancelled. +7. SMALL_COUNT, MEDIUM_COUNT and LARGE_COUNT are the numbers of items of the + region in the three size classes. +8. TOTAL_AMOUNT is the sum of the amounts of all items of the region that are + not cancelled, with 2 decimals. + +# 6. Constraints +- Release target: ABAP Platform 2025 (SAP_BASIS 816). +- Coding standards: Clean ABAP. Keep methods below 40 statements. Do not + create ATC findings of priority 1 or 2. +- Out of scope: do not change {{P}}SALE. + +# 7. Acceptance +- The view is active and has no syntax error. +- The hidden tests pass. +- Write ABAP Unit tests with CL_CDS_TEST_ENVIRONMENT in a global test class. diff --git a/tasks_gen/eval/G0019/task.json b/tasks_gen/eval/G0019/task.json new file mode 100644 index 0000000..2a8f52c --- /dev/null +++ b/tasks_gen/eval/G0019/task.json @@ -0,0 +1,60 @@ +{ + "id": "G0019", + "category": "B", + "object_type": "DDLS", + "difficulty": 2, + "release_target": "v816", + "expected_outcome": "implement", + "budget": { + "max_tool_calls": 40, + "max_activations": 10 + }, + "seed": [ + { + "type": "TABL", + "name": "{{P}}SALE", + "file": "seed/sale.tabl.asabap", + "description": "Sales item" + } + ], + "contract": [ + { + "type": "DDLS", + "name": "{{P}}I_SALES_CLASS", + "fields": [ + "REGION", + "ITEM_COUNT", + "SMALL_COUNT", + "MEDIUM_COUNT", + "LARGE_COUNT", + "TOTAL_AMOUNT" + ] + } + ], + "out_of_scope": [ + "{{P}}SALE" + ], + "hidden_tests": [ + { + "type": "CLAS", + "name": "{{P}}T20_HIDDEN", + "file": "hidden/t20_hidden.clas.abap", + "description": "T20 hidden tests" + } + ], + "reference": [ + { + "type": "DDLS", + "name": "{{P}}I_SALES_CLASS", + "file": "reference/i_sales_class.ddls.asddls", + "description": "Sales items grouped by region with size classes" + }, + { + "type": "CLAS", + "name": "{{P}}T20_TEST", + "file": "reference/t20_test.clas.abap", + "description": "T20 own tests" + } + ], + "craft_checks": [] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0020/generation.json b/tasks_gen/eval/G0020/generation.json new file mode 100644 index 0000000..a159924 --- /dev/null +++ b/tasks_gen/eval/G0020/generation.json @@ -0,0 +1,36 @@ +{ + "id": "G0020", + "pool": "eval", + "object_type": "DDLS", + "category": "B", + "attempts": [ + { + "stage": "validate", + "oracle": 0, + "null": 0 + }, + { + "stage": "validate", + "oracle": 0, + "null": 0 + }, + { + "stage": "validate", + "oracle": 0, + "null": 0 + }, + { + "stage": "validate", + "oracle": 0, + "null": 0 + }, + { + "stage": "manual_fix", + "by": "Claude review 2026-10-03", + "change": "Removed literal default values of the CDS parameters p_from and p_to (not allowed in a view entity). The harness G2 check now works for views with parameters.", + "oracle": 100.0, + "null": 0 + } + ], + "accepted": true +} \ No newline at end of file diff --git a/tasks_gen/eval/G0020/hidden/t20_hidden.clas.abap b/tasks_gen/eval/G0020/hidden/t20_hidden.clas.abap new file mode 100644 index 0000000..82a66a4 --- /dev/null +++ b/tasks_gen/eval/G0020/hidden/t20_hidden.clas.abap @@ -0,0 +1,157 @@ +CLASS {{p}}t20_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC + FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + CLASS-DATA go_env TYPE REF TO if_cds_test_environment. + CLASS-METHODS class_setup. + CLASS-METHODS class_teardown. + METHODS setup. + METHODS revenue_in_period FOR TESTING. + METHODS sold_quantity_in_period FOR TESTING. + METHODS books_in_period FOR TESTING. + METHODS title_and_category FOR TESTING. + METHODS revenue_has_two_decimals FOR TESTING. + METHODS period_limits_included FOR TESTING. + METHODS empty_period FOR TESTING. + METHODS book_outside_period_hidden FOR TESTING. +ENDCLASS. + + +CLASS {{p}}t20_hidden IMPLEMENTATION. + METHOD class_setup. + go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_BOOK_REVENUE' ). + ENDMETHOD. + + METHOD class_teardown. + go_env->destroy( ). + ENDMETHOD. + + METHOD setup. + DATA lt_book TYPE STANDARD TABLE OF {{p}}book WITH EMPTY KEY. + DATA lt_sale TYPE STANDARD TABLE OF {{p}}sale WITH EMPTY KEY. + go_env->clear_doubles( ). + lt_book = VALUE #( + ( book_id = 'B1' title = 'ABAP Basics' category = 'TECH' price = '40.00' ) + ( book_id = 'B2' title = 'SQL Guide' category = 'TECH' price = '25.50' ) + ( book_id = 'B3' title = 'Cooking Fun' category = 'HOME' price = '15.00' ) + ( book_id = 'B4' title = 'Space Novel' category = 'FICTION' price = '9.99' ) + ( book_id = 'B5' title = 'Old Book' category = 'TECH' price = '5.00' ) ). + lt_sale = VALUE #( + ( book_id = 'B1' sale_date = '20240110' quantity = 2 ) + ( book_id = 'B1' sale_date = '20240215' quantity = 1 ) + ( book_id = 'B1' sale_date = '20240601' quantity = 5 ) + ( book_id = 'B2' sale_date = '20240120' quantity = 4 ) + ( book_id = 'B3' sale_date = '20240305' quantity = 3 ) + ( book_id = 'B4' sale_date = '20240201' quantity = 1 ) + ( book_id = 'B4' sale_date = '20240202' quantity = 2 ) + ( book_id = 'B5' sale_date = '20240615' quantity = 7 ) ). + go_env->insert_test_data( lt_book ). + go_env->insert_test_data( lt_sale ). + ENDMETHOD. + + METHOD revenue_in_period. + DATA lv_from TYPE d. + DATA lv_to TYPE d. + lv_from = '20240101'. + lv_to = '20240331'. + SELECT SINGLE revenue + FROM {{p}}i_book_revenue( p_from = @lv_from, p_to = @lv_to ) + WHERE book_id = 'B1' + INTO @DATA(lv_revenue). + cl_abap_unit_assert=>assert_equals( + exp = CONV decfloat34( '120.00' ) + act = CONV decfloat34( lv_revenue ) ). + ENDMETHOD. + + METHOD sold_quantity_in_period. + DATA lv_from TYPE d. + DATA lv_to TYPE d. + lv_from = '20240101'. + lv_to = '20240331'. + SELECT SINGLE sold_quantity + FROM {{p}}i_book_revenue( p_from = @lv_from, p_to = @lv_to ) + WHERE book_id = 'B1' + INTO @DATA(lv_sold). + cl_abap_unit_assert=>assert_equals( exp = 3 act = CONV i( lv_sold ) ). + ENDMETHOD. + + METHOD books_in_period. + DATA lv_from TYPE d. + DATA lv_to TYPE d. + lv_from = '20240101'. + lv_to = '20240331'. + SELECT COUNT(*) + FROM {{p}}i_book_revenue( p_from = @lv_from, p_to = @lv_to ) + INTO @DATA(lv_rows). + cl_abap_unit_assert=>assert_equals( exp = 4 act = lv_rows ). + ENDMETHOD. + + METHOD title_and_category. + DATA lv_from TYPE d. + DATA lv_to TYPE d. + lv_from = '20240101'. + lv_to = '20240331'. + SELECT SINGLE title, category + FROM {{p}}i_book_revenue( p_from = @lv_from, p_to = @lv_to ) + WHERE book_id = 'B2' + INTO @DATA(ls_row). + cl_abap_unit_assert=>assert_equals( exp = 'SQL Guide' act = ls_row-title ). + cl_abap_unit_assert=>assert_equals( exp = 'TECH' act = ls_row-category ). + ENDMETHOD. + + METHOD revenue_has_two_decimals. + DATA lv_from TYPE d. + DATA lv_to TYPE d. + lv_from = '20240101'. + lv_to = '20240331'. + SELECT SINGLE revenue + FROM {{p}}i_book_revenue( p_from = @lv_from, p_to = @lv_to ) + WHERE book_id = 'B4' + INTO @DATA(lv_revenue). + cl_abap_unit_assert=>assert_equals( + exp = CONV decfloat34( '29.97' ) + act = CONV decfloat34( lv_revenue ) ). + ENDMETHOD. + + METHOD period_limits_included. + DATA lv_from TYPE d. + DATA lv_to TYPE d. + lv_from = '20240110'. + lv_to = '20240110'. + SELECT SINGLE sold_quantity + FROM {{p}}i_book_revenue( p_from = @lv_from, p_to = @lv_to ) + WHERE book_id = 'B1' + INTO @DATA(lv_sold_b1). + cl_abap_unit_assert=>assert_equals( exp = 2 act = CONV i( lv_sold_b1 ) ). + + lv_from = '20240305'. + lv_to = '20240305'. + SELECT SINGLE sold_quantity + FROM {{p}}i_book_revenue( p_from = @lv_from, p_to = @lv_to ) + WHERE book_id = 'B3' + INTO @DATA(lv_sold_b3). + cl_abap_unit_assert=>assert_equals( exp = 3 act = CONV i( lv_sold_b3 ) ). + ENDMETHOD. + + METHOD empty_period. + DATA lv_from TYPE d. + DATA lv_to TYPE d. + lv_from = '20240501'. + lv_to = '20240531'. + SELECT COUNT(*) + FROM {{p}}i_book_revenue( p_from = @lv_from, p_to = @lv_to ) + INTO @DATA(lv_rows). + cl_abap_unit_assert=>assert_equals( exp = 0 act = lv_rows ). + ENDMETHOD. + + METHOD book_outside_period_hidden. + DATA lv_from TYPE d. + DATA lv_to TYPE d. + lv_from = '20240101'. + lv_to = '20240331'. + SELECT SINGLE book_id + FROM {{p}}i_book_revenue( p_from = @lv_from, p_to = @lv_to ) + WHERE book_id = 'B5' + INTO @DATA(lv_book). + cl_abap_unit_assert=>assert_initial( lv_book ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0020/reference/i_book_revenue.ddls.asddls b/tasks_gen/eval/G0020/reference/i_book_revenue.ddls.asddls new file mode 100644 index 0000000..c2db923 --- /dev/null +++ b/tasks_gen/eval/G0020/reference/i_book_revenue.ddls.asddls @@ -0,0 +1,20 @@ +@AccessControl.authorizationCheck: #NOT_REQUIRED +@EndUserText.label: 'Book revenue in period' +define view entity {{P}}I_BOOK_REVENUE + with parameters + p_from : abap.dats, + p_to : abap.dats + as select from {{p}}book as bk + inner join {{p}}sale as sl on sl.book_id = bk.book_id +{ + key bk.book_id as book_id, + bk.title as title, + bk.category as category, + sum( sl.quantity ) as sold_quantity, + sum( cast( sl.quantity as abap.dec( 10, 0 ) ) * bk.price ) as revenue +} +where sl.sale_date between $parameters.p_from and $parameters.p_to +group by + bk.book_id, + bk.title, + bk.category diff --git a/tasks_gen/eval/G0020/reference/t20_test.clas.abap b/tasks_gen/eval/G0020/reference/t20_test.clas.abap new file mode 100644 index 0000000..cc856e8 --- /dev/null +++ b/tasks_gen/eval/G0020/reference/t20_test.clas.abap @@ -0,0 +1,62 @@ +CLASS {{p}}t20_test DEFINITION PUBLIC FINAL CREATE PUBLIC + FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + CLASS-DATA go_env TYPE REF TO if_cds_test_environment. + CLASS-METHODS class_setup. + CLASS-METHODS class_teardown. + METHODS setup. + METHODS revenue_of_one_book FOR TESTING. + METHODS period_is_inclusive FOR TESTING. +ENDCLASS. + + +CLASS {{p}}t20_test IMPLEMENTATION. + METHOD class_setup. + go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_BOOK_REVENUE' ). + ENDMETHOD. + + METHOD class_teardown. + go_env->destroy( ). + ENDMETHOD. + + METHOD setup. + DATA lt_book TYPE STANDARD TABLE OF {{p}}book WITH EMPTY KEY. + DATA lt_sale TYPE STANDARD TABLE OF {{p}}sale WITH EMPTY KEY. + go_env->clear_doubles( ). + lt_book = VALUE #( + ( book_id = 'A1' title = 'Test Book' category = 'TECH' price = '10.00' ) ). + lt_sale = VALUE #( + ( book_id = 'A1' sale_date = '20240101' quantity = 2 ) + ( book_id = 'A1' sale_date = '20240131' quantity = 3 ) + ( book_id = 'A1' sale_date = '20240201' quantity = 9 ) ). + go_env->insert_test_data( lt_book ). + go_env->insert_test_data( lt_sale ). + ENDMETHOD. + + METHOD revenue_of_one_book. + DATA lv_from TYPE d. + DATA lv_to TYPE d. + lv_from = '20240101'. + lv_to = '20240131'. + SELECT SINGLE sold_quantity, revenue + FROM {{p}}i_book_revenue( p_from = @lv_from, p_to = @lv_to ) + WHERE book_id = 'A1' + INTO @DATA(ls_row). + cl_abap_unit_assert=>assert_equals( exp = 5 act = CONV i( ls_row-sold_quantity ) ). + cl_abap_unit_assert=>assert_equals( + exp = CONV decfloat34( '50.00' ) + act = CONV decfloat34( ls_row-revenue ) ). + ENDMETHOD. + + METHOD period_is_inclusive. + DATA lv_from TYPE d. + DATA lv_to TYPE d. + lv_from = '20240101'. + lv_to = '20240101'. + SELECT SINGLE sold_quantity + FROM {{p}}i_book_revenue( p_from = @lv_from, p_to = @lv_to ) + WHERE book_id = 'A1' + INTO @DATA(lv_sold). + cl_abap_unit_assert=>assert_equals( exp = 2 act = CONV i( lv_sold ) ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0020/seed/book.tabl.asabap b/tasks_gen/eval/G0020/seed/book.tabl.asabap new file mode 100644 index 0000000..688f0a6 --- /dev/null +++ b/tasks_gen/eval/G0020/seed/book.tabl.asabap @@ -0,0 +1,13 @@ +@EndUserText.label : 'Book master data' +@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE +@AbapCatalog.tableCategory : #TRANSPARENT +@AbapCatalog.deliveryClass : #A +@AbapCatalog.dataMaintenance : #RESTRICTED +define table {{p}}book { + key client : abap.clnt not null; + key book_id : abap.char(10) not null; + title : abap.char(40); + category : abap.char(10); + price : abap.dec(15,2); + stock : abap.int4; +} diff --git a/tasks_gen/eval/G0020/seed/sale.tabl.asabap b/tasks_gen/eval/G0020/seed/sale.tabl.asabap new file mode 100644 index 0000000..7842236 --- /dev/null +++ b/tasks_gen/eval/G0020/seed/sale.tabl.asabap @@ -0,0 +1,11 @@ +@EndUserText.label : 'Book sales' +@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE +@AbapCatalog.tableCategory : #TRANSPARENT +@AbapCatalog.deliveryClass : #A +@AbapCatalog.dataMaintenance : #RESTRICTED +define table {{p}}sale { + key client : abap.clnt not null; + key book_id : abap.char(10) not null; + key sale_date : abap.dats not null; + quantity : abap.int4; +} diff --git a/tasks_gen/eval/G0020/spec.md b/tasks_gen/eval/G0020/spec.md new file mode 100644 index 0000000..6953b6a --- /dev/null +++ b/tasks_gen/eval/G0020/spec.md @@ -0,0 +1,43 @@ +# 1. Goal +Show the sales of each book for a period that the caller gives. The period comes +from the caller, so the view needs input parameters. A report and an OData +service read the view. + +# 2. Open questions +None. + +# 3. Context +- Table {{P}}BOOK (book master data), package $TMP. Key: BOOK_ID (CHAR 10). + Fields: TITLE (CHAR 40), CATEGORY (CHAR 10), PRICE (DEC 15,2), STOCK (INT4). +- Table {{P}}SALE (book sales), package $TMP. Key: BOOK_ID (CHAR 10), + SALE_DATE (DATS). Field: QUANTITY (INT4). + +# 4. Contract +- Create the CDS view entity {{P}}I_BOOK_REVENUE in package $TMP. +- The view has two input parameters, with these names and types: + - P_FROM (DATS): first day of the period. + - P_TO (DATS): last day of the period. +- Elements, with these names: BOOK_ID (key), TITLE, CATEGORY, SOLD_QUANTITY, + REVENUE. +- No authorization check (#NOT_REQUIRED). +- Create the global ABAP Unit test class {{P}}T20_TEST for the view. + +# 5. Business rules +1. The view has one row for each book that has at least one sale whose + SALE_DATE is in the period. The period starts on P_FROM and ends on P_TO. + Both limits belong to the period. +2. A book without a sale in the period has no row in the view. +3. SOLD_QUANTITY is the sum of QUANTITY of all sales of the book in the period. +4. REVENUE is the sum of QUANTITY multiplied by PRICE of the book, over all + sales of the book in the period. REVENUE has 2 decimals. +5. TITLE and CATEGORY are the title and the category of the book. + +# 6. Constraints +- Release target: 7.58. +- Coding standards: Clean ABAP, methods below 40 statements. +- Out of scope: do not change {{P}}BOOK and {{P}}SALE. + +# 7. Acceptance +- The view is active and has no syntax error. +- The hidden tests pass. +- {{P}}T20_TEST uses CL_CDS_TEST_ENVIRONMENT and passes. diff --git a/tasks_gen/eval/G0020/task.json b/tasks_gen/eval/G0020/task.json new file mode 100644 index 0000000..f517d64 --- /dev/null +++ b/tasks_gen/eval/G0020/task.json @@ -0,0 +1,76 @@ +{ + "id": "G0020", + "category": "B", + "object_type": "DDLS", + "difficulty": 2, + "release_target": "v758", + "expected_outcome": "implement", + "budget": { + "max_tool_calls": 40, + "max_activations": 10 + }, + "seed": [ + { + "type": "TABL", + "name": "{{P}}BOOK", + "file": "seed/book.tabl.asabap", + "description": "Book master data" + }, + { + "type": "TABL", + "name": "{{P}}SALE", + "file": "seed/sale.tabl.asabap", + "description": "Book sales" + } + ], + "contract": [ + { + "type": "DDLS", + "name": "{{P}}I_BOOK_REVENUE", + "fields": [ + "BOOK_ID", + "TITLE", + "CATEGORY", + "SOLD_QUANTITY", + "REVENUE" + ], + "parameters": [ + { + "name": "P_FROM", + "type": "DATS" + }, + { + "name": "P_TO", + "type": "DATS" + } + ] + } + ], + "out_of_scope": [ + "{{P}}BOOK", + "{{P}}SALE" + ], + "hidden_tests": [ + { + "type": "CLAS", + "name": "{{P}}T20_HIDDEN", + "file": "hidden/t20_hidden.clas.abap", + "description": "T20 hidden tests" + } + ], + "reference": [ + { + "type": "DDLS", + "name": "{{P}}I_BOOK_REVENUE", + "file": "reference/i_book_revenue.ddls.asddls", + "description": "Book revenue in a period" + }, + { + "type": "CLAS", + "name": "{{P}}T20_TEST", + "file": "reference/t20_test.clas.abap", + "description": "T20 own tests" + } + ], + "craft_checks": [] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0021/generation.json b/tasks_gen/eval/G0021/generation.json new file mode 100644 index 0000000..36f64d3 --- /dev/null +++ b/tasks_gen/eval/G0021/generation.json @@ -0,0 +1,29 @@ +{ + "id": "G0021", + "pool": "eval", + "object_type": "DDLS", + "category": "B", + "attempts": [ + { + "stage": "validate", + "oracle": 0, + "null": 0 + }, + { + "stage": "validate", + "oracle": 0, + "null": 0 + }, + { + "stage": "validate", + "oracle": 0, + "null": 0 + }, + { + "stage": "validate", + "oracle": 100.0, + "null": 0 + } + ], + "accepted": true +} \ No newline at end of file diff --git a/tasks_gen/eval/G0021/hidden/t16_hidden.clas.abap b/tasks_gen/eval/G0021/hidden/t16_hidden.clas.abap new file mode 100644 index 0000000..923b071 --- /dev/null +++ b/tasks_gen/eval/G0021/hidden/t16_hidden.clas.abap @@ -0,0 +1,93 @@ +CLASS {{p}}t16_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC + FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + CLASS-DATA go_env TYPE REF TO if_cds_test_environment. + CLASS-METHODS class_setup. + CLASS-METHODS class_teardown. + METHODS setup. + METHODS road_rows FOR TESTING. + METHODS rail_rows FOR TESTING. + METHODS row_count FOR TESTING. + METHODS road_customer FOR TESTING. + METHODS rail_customer FOR TESTING. + METHODS road_weight FOR TESTING. + METHODS rail_cost FOR TESTING. + METHODS light_shipments_hidden FOR TESTING. +ENDCLASS. + + +CLASS {{p}}t16_hidden IMPLEMENTATION. + + METHOD class_setup. + go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_SHIPMENT' ). + ENDMETHOD. + + METHOD class_teardown. + go_env->destroy( ). + ENDMETHOD. + + METHOD setup. + DATA lt_road TYPE STANDARD TABLE OF {{p}}road WITH EMPTY KEY. + DATA lt_rail TYPE STANDARD TABLE OF {{p}}rail WITH EMPTY KEY. + + go_env->clear_doubles( ). + + lt_road = VALUE #( + ( ship_id = 'R1' cust = 'ALPHA' kg = '10.500' cost = '100.00' ) + ( ship_id = 'R2' cust = 'BETA' kg = '2.000' cost = '50.00' ) + ( ship_id = 'R3' cust = 'GAMMA' kg = '0.000' cost = '10.00' ) ). + lt_rail = VALUE #( + ( ship_id = 'L1' customer = 'DELTA' weight = '20.000' fee = '300.00' ) + ( ship_id = 'L2' customer = 'ALPHA' weight = '5.000' fee = '75.00' ) + ( ship_id = 'L3' customer = 'EPSILON' weight = '-1.000' fee = '5.00' ) ). + + go_env->insert_test_data( lt_road ). + go_env->insert_test_data( lt_rail ). + ENDMETHOD. + + METHOD road_rows. + SELECT SINGLE ship_mode FROM {{p}}i_shipment WHERE ship_id = 'R1' INTO @DATA(lv_ship_mode). + cl_abap_unit_assert=>assert_equals( exp = 'ROAD' act = lv_ship_mode ). + ENDMETHOD. + + METHOD rail_rows. + SELECT SINGLE ship_mode FROM {{p}}i_shipment WHERE ship_id = 'L1' INTO @DATA(lv_ship_mode). + cl_abap_unit_assert=>assert_equals( exp = 'RAIL' act = lv_ship_mode ). + ENDMETHOD. + + METHOD row_count. + SELECT COUNT(*) FROM {{p}}i_shipment INTO @DATA(lv_count). + cl_abap_unit_assert=>assert_equals( exp = 4 act = lv_count ). + ENDMETHOD. + + METHOD road_customer. + SELECT SINGLE customer FROM {{p}}i_shipment WHERE ship_id = 'R2' INTO @DATA(lv_customer). + cl_abap_unit_assert=>assert_equals( exp = 'BETA' act = lv_customer ). + ENDMETHOD. + + METHOD rail_customer. + SELECT SINGLE customer FROM {{p}}i_shipment WHERE ship_id = 'L2' INTO @DATA(lv_customer). + cl_abap_unit_assert=>assert_equals( exp = 'ALPHA' act = lv_customer ). + ENDMETHOD. + + METHOD road_weight. + SELECT SINGLE weight FROM {{p}}i_shipment WHERE ship_id = 'R1' INTO @DATA(lv_weight). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '10.5' ) + act = CONV decfloat34( lv_weight ) ). + ENDMETHOD. + + METHOD rail_cost. + SELECT SINGLE cost FROM {{p}}i_shipment WHERE ship_id = 'L1' INTO @DATA(lv_cost). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '300' ) + act = CONV decfloat34( lv_cost ) ). + ENDMETHOD. + + METHOD light_shipments_hidden. + DATA lv_count TYPE i. + SELECT COUNT(*) FROM {{p}}i_shipment WHERE ship_id = 'R3' INTO @lv_count. + cl_abap_unit_assert=>assert_equals( exp = 0 act = lv_count ). + SELECT COUNT(*) FROM {{p}}i_shipment WHERE ship_id = 'L3' INTO @lv_count. + cl_abap_unit_assert=>assert_equals( exp = 0 act = lv_count ). + ENDMETHOD. + +ENDCLASS. diff --git a/tasks_gen/eval/G0021/reference/i_shipment.ddls.asddls b/tasks_gen/eval/G0021/reference/i_shipment.ddls.asddls new file mode 100644 index 0000000..e488992 --- /dev/null +++ b/tasks_gen/eval/G0021/reference/i_shipment.ddls.asddls @@ -0,0 +1,23 @@ +@AccessControl.authorizationCheck: #NOT_REQUIRED +@Metadata.ignorePropagatedAnnotations: true +@EndUserText.label: 'Shipment overview' +define view entity {{P}}I_SHIPMENT + as select from {{p}}road as road +{ + key road.ship_id as ship_id, + road.cust as customer, + 'ROAD' as ship_mode, + road.kg as weight, + road.cost as cost +} +where road.kg > 0 +union all +select from {{p}}rail as rail +{ + key rail.ship_id as ship_id, + rail.customer as customer, + 'RAIL' as ship_mode, + rail.weight as weight, + rail.fee as cost +} +where rail.weight > 0 diff --git a/tasks_gen/eval/G0021/reference/t16_test.clas.abap b/tasks_gen/eval/G0021/reference/t16_test.clas.abap new file mode 100644 index 0000000..1339d2c --- /dev/null +++ b/tasks_gen/eval/G0021/reference/t16_test.clas.abap @@ -0,0 +1,46 @@ +CLASS {{p}}t16_test DEFINITION PUBLIC FINAL CREATE PUBLIC + FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + CLASS-DATA go_env TYPE REF TO if_cds_test_environment. + CLASS-METHODS class_setup. + CLASS-METHODS class_teardown. + METHODS setup. + METHODS both_sources FOR TESTING. + METHODS mode_literal FOR TESTING. +ENDCLASS. + + +CLASS {{p}}t16_test IMPLEMENTATION. + + METHOD class_setup. + go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_SHIPMENT' ). + ENDMETHOD. + + METHOD class_teardown. + go_env->destroy( ). + ENDMETHOD. + + METHOD setup. + DATA lt_road TYPE STANDARD TABLE OF {{p}}road WITH EMPTY KEY. + DATA lt_rail TYPE STANDARD TABLE OF {{p}}rail WITH EMPTY KEY. + + go_env->clear_doubles( ). + + lt_road = VALUE #( ( ship_id = 'R1' cust = 'C1' kg = '1.000' cost = '10.00' ) ). + lt_rail = VALUE #( ( ship_id = 'L1' customer = 'C2' weight = '2.000' fee = '20.00' ) ). + + go_env->insert_test_data( lt_road ). + go_env->insert_test_data( lt_rail ). + ENDMETHOD. + + METHOD both_sources. + SELECT COUNT(*) FROM {{p}}i_shipment INTO @DATA(lv_count). + cl_abap_unit_assert=>assert_equals( exp = 2 act = lv_count ). + ENDMETHOD. + + METHOD mode_literal. + SELECT SINGLE ship_mode FROM {{p}}i_shipment WHERE ship_id = 'L1' INTO @DATA(lv_ship_mode). + cl_abap_unit_assert=>assert_equals( exp = 'RAIL' act = lv_ship_mode ). + ENDMETHOD. + +ENDCLASS. diff --git a/tasks_gen/eval/G0021/seed/rail.tabl.asabap b/tasks_gen/eval/G0021/seed/rail.tabl.asabap new file mode 100644 index 0000000..57b83d0 --- /dev/null +++ b/tasks_gen/eval/G0021/seed/rail.tabl.asabap @@ -0,0 +1,12 @@ +@EndUserText.label : 'Rail shipment' +@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE +@AbapCatalog.tableCategory : #TRANSPARENT +@AbapCatalog.deliveryClass : #A +@AbapCatalog.dataMaintenance : #RESTRICTED +define table {{p}}rail { + key client : abap.clnt not null; + key ship_id : abap.char(10) not null; + customer : abap.char(10); + weight : abap.dec(15,3); + fee : abap.dec(15,2); +} diff --git a/tasks_gen/eval/G0021/seed/road.tabl.asabap b/tasks_gen/eval/G0021/seed/road.tabl.asabap new file mode 100644 index 0000000..73fd115 --- /dev/null +++ b/tasks_gen/eval/G0021/seed/road.tabl.asabap @@ -0,0 +1,12 @@ +@EndUserText.label : 'Road shipment' +@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE +@AbapCatalog.tableCategory : #TRANSPARENT +@AbapCatalog.deliveryClass : #A +@AbapCatalog.dataMaintenance : #RESTRICTED +define table {{p}}road { + key client : abap.clnt not null; + key ship_id : abap.char(10) not null; + cust : abap.char(10); + kg : abap.dec(15,3); + cost : abap.dec(15,2); +} diff --git a/tasks_gen/eval/G0021/seed/seed_ship.clas.abap b/tasks_gen/eval/G0021/seed/seed_ship.clas.abap new file mode 100644 index 0000000..e0d097a --- /dev/null +++ b/tasks_gen/eval/G0021/seed/seed_ship.clas.abap @@ -0,0 +1,31 @@ +CLASS {{p}}seed_ship DEFINITION PUBLIC FINAL CREATE PUBLIC. + PUBLIC SECTION. + INTERFACES if_oo_adt_classrun. +ENDCLASS. + + +CLASS {{p}}seed_ship IMPLEMENTATION. + METHOD if_oo_adt_classrun~main. + DATA lt_road TYPE STANDARD TABLE OF {{p}}road WITH EMPTY KEY. + DATA lt_rail TYPE STANDARD TABLE OF {{p}}rail WITH EMPTY KEY. + + DELETE FROM {{p}}road. + DELETE FROM {{p}}rail. + + lt_road = VALUE #( + ( client = sy-mandt ship_id = 'R1' cust = 'ALPHA' kg = '10.500' cost = '100.00' ) + ( client = sy-mandt ship_id = 'R2' cust = 'BETA' kg = '2.000' cost = '50.00' ) + ( client = sy-mandt ship_id = 'R3' cust = 'GAMMA' kg = '0.000' cost = '10.00' ) + ( client = sy-mandt ship_id = 'R4' cust = 'DELTA' kg = '7.250' cost = '80.00' ) ). + INSERT {{p}}road FROM TABLE @lt_road. + + lt_rail = VALUE #( + ( client = sy-mandt ship_id = 'L1' customer = 'DELTA' weight = '20.000' fee = '300.00' ) + ( client = sy-mandt ship_id = 'L2' customer = 'ALPHA' weight = '5.000' fee = '75.00' ) + ( client = sy-mandt ship_id = 'L3' customer = 'EPSILON' weight = '-1.000' fee = '5.00' ) + ( client = sy-mandt ship_id = 'L4' customer = 'BETA' weight = '12.000' fee = '150.00' ) ). + INSERT {{p}}rail FROM TABLE @lt_rail. + + out->write( 'Seed data inserted' ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0021/spec.md b/tasks_gen/eval/G0021/spec.md new file mode 100644 index 0000000..7d691d6 --- /dev/null +++ b/tasks_gen/eval/G0021/spec.md @@ -0,0 +1,42 @@ +# 1. Goal +Show all shipments in one list. The shipments come from two tables: road shipments +and rail shipments. The list must also show the transport mode as text. A report +and an OData service will read the list, so the union must be in the database, in +a CDS view. + +# 2. Open questions +None. + +# 3. Context +- Table {{P}}ROAD (road shipments), package $TMP. Key: SHIP_ID (CHAR 10). + Fields: CUST (CHAR 10, customer), KG (DEC 15,3, weight), COST (DEC 15,2). +- Table {{P}}RAIL (rail shipments), package $TMP. Key: SHIP_ID (CHAR 10). + Fields: CUSTOMER (CHAR 10, customer), WEIGHT (DEC 15,3, weight), + FEE (DEC 15,2). +- The two tables use different column names for the same content. +- Both tables have data. A seed class fills them. + +# 4. Contract +- Create the CDS view entity {{P}}I_SHIPMENT in package $TMP. +- The elements have these names: SHIP_ID, CUSTOMER, SHIP_MODE, WEIGHT, COST. +- No authorization check (#NOT_REQUIRED). + +# 5. Business rules +1. The view is the union of {{P}}ROAD and {{P}}RAIL. Each row of {{P}}ROAD gives + one row of the view, and each row of {{P}}RAIL gives one row of the view. +2. SHIP_MODE is the literal 'ROAD' for a row that comes from {{P}}ROAD. + SHIP_MODE is the literal 'RAIL' for a row that comes from {{P}}RAIL. +3. CUSTOMER is CUST for a road shipment and CUSTOMER for a rail shipment. +4. WEIGHT is KG for a road shipment and WEIGHT for a rail shipment. +5. COST is COST for a road shipment and FEE for a rail shipment. +6. A shipment with a weight that is not positive (0 or less) is not in the view. + +# 6. Constraints +- Release target: 7.58. +- Coding standards: Clean ABAP. +- Out of scope: do not change {{P}}ROAD and {{P}}RAIL. + +# 7. Acceptance +- The view is active and has no syntax error. +- The hidden tests pass. +- Write ABAP Unit tests with CL_CDS_TEST_ENVIRONMENT in a global test class. diff --git a/tasks_gen/eval/G0021/task.json b/tasks_gen/eval/G0021/task.json new file mode 100644 index 0000000..0d3420d --- /dev/null +++ b/tasks_gen/eval/G0021/task.json @@ -0,0 +1,73 @@ +{ + "id": "G0021", + "category": "B", + "object_type": "DDLS", + "difficulty": 2, + "release_target": "v758", + "expected_outcome": "implement", + "budget": { + "max_tool_calls": 40, + "max_activations": 10 + }, + "seed": [ + { + "type": "TABL", + "name": "{{P}}ROAD", + "file": "seed/road.tabl.asabap", + "description": "Road shipments" + }, + { + "type": "TABL", + "name": "{{P}}RAIL", + "file": "seed/rail.tabl.asabap", + "description": "Rail shipments" + }, + { + "type": "CLAS", + "name": "{{P}}SEED_SHIP", + "file": "seed/seed_ship.clas.abap", + "description": "Seed data for both shipment tables", + "run": true + } + ], + "contract": [ + { + "type": "DDLS", + "name": "{{P}}I_SHIPMENT", + "fields": [ + "SHIP_ID", + "CUSTOMER", + "SHIP_MODE", + "WEIGHT", + "COST" + ] + } + ], + "out_of_scope": [ + "{{P}}ROAD", + "{{P}}RAIL" + ], + "hidden_tests": [ + { + "type": "CLAS", + "name": "{{P}}T16_HIDDEN", + "file": "hidden/t16_hidden.clas.abap", + "description": "T16 hidden tests" + } + ], + "reference": [ + { + "type": "DDLS", + "name": "{{P}}I_SHIPMENT", + "file": "reference/i_shipment.ddls.asddls", + "description": "Union view of road and rail shipments with a literal mode" + }, + { + "type": "CLAS", + "name": "{{P}}T16_TEST", + "file": "reference/t16_test.clas.abap", + "description": "T16 own tests" + } + ], + "craft_checks": [] +} \ No newline at end of file