diff --git a/CLAUDE.md b/CLAUDE.md index 0f153b0..3c7521c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -80,7 +80,8 @@ python3 -m harness.cli run T01 --agent oracle|null|llm [--model NAME] --run N python3 -m harness.cli rescore runs/ python3 -m harness.cli cleanup-list runs/ && python3 -m harness.cli teardown runs/ python3 -m harness.generator --id G0100 --pool eval --object-type CLAS --category C --run-base 2000 -python3 -m harness.pilot 2 +python3 -m harness.pilot 2 # pilot list; `python3 -m harness.pilot 22 hard 1400` hard batch +python3 -m harness.mutation G0002 --pool eval --run-base 5000 [--keep] python3 -c "from harness.ledger import spent; print(spent())" ``` @@ -89,18 +90,18 @@ 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. -- 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 80k, 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. +- Pilot (G0002–G0021): 16/20 accepted, 0.16 USD per accepted task (`docs/faz1-tasarim.md` 11e). + G0020 and G0013 fixed in review and revalidated → 18/20. +- Hard batch (G0022–G0026, 2026-10-03): 5/5 accepted (G0022 after harness fixes), 0.097 USD per accepted + task, first-attempt 1/5 (`docs/faz1-tasarim.md` 11f). +- Generator: static checks, local abaplint parser check, dependency order of seed/reference, budget floor, + max_tokens 80k. Runner: G2 for CDS with parameters, G6 ignores unknown standard superclasses. +- Step D: `harness/mutation.py` (deterministic mutants of the reference, hidden tests must fail). ## 7. Next steps -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). +1. Step D: run the mutation check on all accepted tasks; integrate it into the generator. +2. Use killed mutants as faulty references for own-test scoring (component (a)). 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). diff --git a/docs/faz1-tasarim.md b/docs/faz1-tasarim.md index 3a3883d..8805059 100644 --- a/docs/faz1-tasarim.md +++ b/docs/faz1-tasarim.md @@ -576,6 +576,35 @@ Diğer bulgular: - Ü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). +## 11f. Zor görev partisi (2026-10-03, G0022–G0026) + +Amaç: pilot sonrası iyileştirmeleri pilotta düşen tiplerle ölçmek. Görevler: CLAS D (exception), CLAS E (packed sayılı eski kod, `LENGTH` tuzağı), parametreli CDS, UNION'lı CDS, FM. Sonuçlar: `runs/gen/hard.json`. + +| | Pilot (20) | Parti (5, zor) | +|---|---|---| +| İlk denemede kabul | 3/20 (%15) | 1/5 (%20): G0023 | +| Toplam kabul | 16/20 | 4/5 üretimde; G0022 harness düzeltmesinden sonra → 5/5 | +| LLM çağrısı / görev | 2,75 | 2,6 | +| Maliyet / kabul edilen görev | 0,16 $ | **0,097 $** (0,39 $ / 5) | +| En büyük çıktı | 393k (kaçak) | 61,5k | + +SAP'ye gitmeden yakalanan hata: **2** (abaplint `parser_error`, G0026 ×2). Statik kontroller bu partide hata bulmadı: 30 karakter, tablo tipi, `LENGTH` hataları tekrar etmedi (prompt kuralları etkili). + +SAP'de düşen 8 doğrulama: + +| Neden | Sayı | Durum | +|---|---|---| +| Harness: referans objeler bağımlılık sırasında kurulmuyor (G0022, G0026) | 2 | Düzeltildi: generator seed/reference listesini sıraya dizer | +| Harness: abaplint `CX_STATIC_CHECK`'i tanımıyor → exception hiyerarşisi G6'da düşer (G0022). LLM koşularında da kategori D'yi 0'a indirirdi | 1 | Düzeltildi: G6 bu mesajı yok sayar (G1 aktivasyonu kontrol eder) | +| CDS view entity'de `:p_from` (G0024) | 1 | Statik kontrol + prompt kuralı | +| UNION'da `@Metadata.ignorePropagatedAnnotations` yok (G0025) | 1 | Statik kontrol + prompt kuralı | +| Referansın kendi testlerinde tip hatası (G0022) | 2 | Onarım turu | +| Abaplint hatası; asıl neden G6 harness hatasıydı (G0022) | 1 | Düzeltildi | + +Ek bulgu: G0022'nin bütçesi (12 aktivasyon) referansın kendisine (13) yetmiyordu → oracle disiplinden 5 puan kaybetti. Generator artık bütçeye alt sınır koyar: aktivasyon ≥ 2 × oracle, tool çağrısı ≥ 3 × oracle. Mevcut görevlerden sadece G0022 etkilendi. + +G0022 bundle'ı değiştirilmeden yeniden doğrulandı (harness düzeltmeleri + bütçe): oracle 100, null 0 → kabul. + ## 12. Açık sorular Cevaplananlar (Adım 1.2): diff --git a/docs/yol-haritasi.md b/docs/yol-haritasi.md index 7555c8b..303d087 100644 --- a/docs/yol-haritasi.md +++ b/docs/yol-haritasi.md @@ -86,8 +86,8 @@ Adım 3 görev ister; görevler el ile yazılırsa darboğaz olur. Bu yüzden il | 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 | +| D | Otomatik görev doğrulama: oracle = 100, null = 0, gizli testler bozuk referansta düşmeli | oracle/null hazır; mutasyon kontrolü `harness/mutation.py` (2026-10-03) | +| 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). Zor parti (5 görev): 5/5 kabul, 0,097 $ / kabul edilen görev (11f) | | 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) | | diff --git a/harness/generator.py b/harness/generator.py index e98ede4..265a6e2 100644 --- a/harness/generator.py +++ b/harness/generator.py @@ -49,8 +49,10 @@ Rules for the bundle: - 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. +- CDS: use "define view entity". Parameters have no default value. Use a parameter as + $parameters.p_name (not :p_name). In a UNION, all branches have the same + key elements and element names, and the view + has the annotation @Metadata.ignorePropagatedAnnotations: true. 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. @@ -178,6 +180,14 @@ def lint_files(b): 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 rel, src in b.get("files", {}).items(): + if rel.endswith(".asddls") and re.search(r"define\s+(root\s+)?view\s+entity", src, re.I): + if re.search(r"\bunion\b", src, re.I) and not re.search(r"@Metadata\.ignorePropagatedAnnotations\s*:\s*true", + src, re.I): + errs.append(f"{rel}: a view entity with UNION needs @Metadata.ignorePropagatedAnnotations: true") + for p in sorted(set(re.findall(r"[=<>(,]\s*:(\w+)|\bbetween\s+:(\w+)|\band\s+:(\w+)", src, re.I))): + name = next(x for x in p if x) + errs.append(f"{rel}: parameter :{name}; a view entity needs $parameters.{name}") for k in ("seed", "reference"): for o in t.get(k, []): m = DDL_KIND.search(b.get("files", {}).get(o.get("file", ""), "")) @@ -279,11 +289,23 @@ def dependency_order(objs, files): return out +def floor_budget(task): + """The model needs more calls than the oracle (reads, repairs). G0022: budget 12 activations, + reference alone 13. Minimum: 2 x oracle activations, 3 x oracle tool calls.""" + ref = task.get("reference", []) + pushes = sum(bool(o.get("file")) + bool(o.get("testclasses_file")) for o in ref) + calls = len(ref) + pushes + bud = task.setdefault("budget", {}) + bud["max_activations"] = max(int(bud.get("max_activations", 0)), 2 * pushes) + bud["max_tool_calls"] = max(int(bud.get("max_tool_calls", 0)), 3 * calls) + + def write_bundle(b, task_dir, task_id): os.makedirs(task_dir, exist_ok=True) b["task"]["id"] = task_id for k in ("seed", "reference"): b["task"][k] = dependency_order(b["task"].get(k, []), b["files"]) + floor_budget(b["task"]) json.dump(b["task"], open(os.path.join(task_dir, "task.json"), "w"), indent=2) for rel, content in b["files"].items(): path = os.path.join(task_dir, rel) diff --git a/harness/mutation.py b/harness/mutation.py new file mode 100644 index 0000000..08437a3 --- /dev/null +++ b/harness/mutation.py @@ -0,0 +1,188 @@ +"""Mutation check (step D): the hidden tests must fail on a broken reference. + +Small deterministic changes (mutants) go into the main source of the contract objects of the reference. +For each mutant the oracle writes the mutated reference and the harness runs the hidden tests. + killed = reference active and at least one hidden test fails + survived = reference active and all hidden tests pass (a test gap or an equivalent mutant) + invalid = the mutant does not activate (not counted) + + python3 -m harness.mutation G0002 G0003 --pool eval --run-base 5000 +""" +import argparse +import json +import os +import random +import re +import shutil + +from .adt_client import load_env +from .agents import OracleAgent +from .runner import Runner + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +MAX_MUTANTS = 5 +MIN_VALID = 3 +MIN_KILL_RATE = 0.75 + +# (kind, pattern, replacement); patterns match only outside comments and literals +ABAP_OPS = [ + ("rel", r"(?<=\s)>=(?=\s)", "<"), ("rel", r"(?<=\s)<=(?=\s)", ">"), + ("rel", r"(?<=\s)>(?=\s)", ">="), ("rel", r"(?<=\s)<(?=\s)", "<="), + ("rel", r"(?<=\s)<>(?=\s)", "="), + ("rel", r"\bGE\b", "LT"), ("rel", r"\bLE\b", "GT"), ("rel", r"\bGT\b", "GE"), ("rel", r"\bLT\b", "LE"), + ("rel", r"\bNE\b", "EQ"), + ("logic", r"(?<=\s)AND(?=\s)", "OR"), ("logic", r"(?<=\s)OR(?=\s)", "AND"), + ("arith", r"(?<=\s)\+(?=\s)", "-"), ("arith", r"(?<=\s)-(?=\s)", "+"), ("arith", r"(?<=\s)\*(?=\s)", "/"), + ("bool", r"\babap_true\b", "abap_false"), ("bool", r"\babap_false\b", "abap_true"), + ("const", r"(?=(?=\s)", "<"), ("rel", r"(?<=\s)<=(?=\s)", ">"), + ("rel", r"(?<=\s)>(?=\s)", ">="), ("rel", r"(?<=\s)<(?=\s)", "<="), ("rel", r"(?<=\s)<>(?=\s)", "="), + ("logic", r"(?<=\s)and(?=\s)", "or"), + ("arith", r"(?<=\s)\+(?=\s)", "-"), ("arith", r"(?<=\s)-(?=\s)", "+"), + ("agg", r"\bsum\s*\(", "max("), ("agg", r"\bcount\s*\(", "max("), ("agg", r"\bavg\s*\(", "max("), + ("join", r"\binner\s+join\b", "left outer join"), + ("const", r"(?= 0 else len(src)] + if _skip_line(line) or (kind == "const" and re.search(r"\bLENGTH\b|\bDECIMALS\b|\(\s*\d+\s*,", line, re.I)): + continue + new = str(int(x.group(1)) + 1) if rep is None else rep + sites.append((kind, x.start(), x.end(), new, src.count("\n", 0, x.start()) + 1, x.group(0))) + rnd = random.Random(seed) + rnd.shuffle(sites) + chosen, kinds, lines = [], set(), set() + for prefer_new in (True, False): + for s in sites: + if len(chosen) >= n: + break + if s in chosen or s[4] in lines or (prefer_new and s[0] in kinds): + continue + chosen.append(s) + kinds.add(s[0]) + lines.add(s[4]) + out = [] + for kind, a, b, new, line, old in sorted(chosen, key=lambda s: s[1]): + out.append((f"line {line}: {old.strip()} -> {new} ({kind})", src[:a] + new + src[b:])) + return out + + +def check_task(pool_root, task_id, run_base, n=MAX_MUTANTS, keep=False): + """Run the mutants of one task. Returns a summary dict; writes it to /mutation.json.""" + task_dir = os.path.join(pool_root, task_id) + meta = json.load(open(os.path.join(task_dir, "task.json"))) + contract = {c["name"].upper() for c in meta["contract"]} + targets = [o for o in meta["reference"] if o["name"].upper() in contract and o.get("file") + and o["type"] in ("CLAS", "FUNC", "PROG", "DDLS")] + work = os.path.join(ROOT, "runs", "gen", "mut") + os.makedirs(work, exist_ok=True) + runner = Runner(os.path.join(work, "pool"), os.path.join(work, "runs")) + # candidates per object, then round robin: objects without mutation sites (exception classes) give their share + cand = [[(o, d, m) for d, m in mutants(open(os.path.join(task_dir, o["file"])).read(), o["type"], + f"{task_id}:{o['name']}", n)] for o in targets] + plan = [] + while len(plan) < n and any(cand): + for c in cand: + if c and len(plan) < n: + plan.append(c.pop(0)) + results = [] + for k, (o, desc, msrc) in enumerate(plan): + mdir = os.path.join(work, "pool", task_id) + shutil.rmtree(mdir, ignore_errors=True) + shutil.copytree(task_dir, mdir) + open(os.path.join(mdir, o["file"]), "w").write(msrc) + rep, _ = runner.run(task_id, OracleAgent(), run_base + k) + h = rep.get("hidden_tests") or {} + g = rep.get("gates") or {} + if not g.get("G1_active") or not h.get("total"): + status = "invalid" + elif h["passed"] < h["total"]: + status = "killed" + else: + status = "survived" + results.append({"object": o["name"], "mutant": desc, "status": status, + "hidden": f"{h.get('passed')}/{h.get('total')}", + "failed_tests": [d["method"] for d in h.get("detail", []) if not d["ok"]]}) + if keep and status == "killed": # candidate faulty reference for own-test scoring + fdir = os.path.join(task_dir, "faulty") + os.makedirs(fdir, exist_ok=True) + open(os.path.join(fdir, f"m{k}_{os.path.basename(o['file'])}"), "w").write(msrc) + shutil.rmtree(os.path.join(work, "pool", task_id), ignore_errors=True) + valid = [r for r in results if r["status"] != "invalid"] + killed = [r for r in valid if r["status"] == "killed"] + summary = {"task": task_id, "mutants": results, "valid": len(valid), "killed": len(killed), + "kill_rate": round(len(killed) / len(valid), 2) if valid else None, + "ok": len(valid) >= MIN_VALID and len(killed) / max(1, len(valid)) >= MIN_KILL_RATE} + if len(valid) < MIN_VALID: + summary["note"] = f"fewer than {MIN_VALID} valid mutants; check by review" + json.dump(summary, open(os.path.join(task_dir, "mutation.json"), "w"), indent=1) + return summary + + +def main(): + load_env(os.path.join(ROOT, ".env")) + ap = argparse.ArgumentParser() + ap.add_argument("tasks", nargs="+") + ap.add_argument("--pool", default="eval") + ap.add_argument("--run-base", type=int, required=True) + ap.add_argument("-n", type=int, default=MAX_MUTANTS) + ap.add_argument("--keep", action="store_true", help="store killed mutants in /faulty/") + a = ap.parse_args() + root = os.path.join(ROOT, "tasks_gen", a.pool) if a.pool != "tasks" else os.path.join(ROOT, "tasks") + for i, t in enumerate(a.tasks): + s = check_task(root, t, a.run_base + 10 * i, a.n, a.keep) + print(json.dumps({k: v for k, v in s.items() if k != "mutants"}), flush=True) + for m in s["mutants"]: + print(" ", m["status"], m["object"], m["mutant"], m["hidden"], flush=True) + + +if __name__ == "__main__": + main() diff --git a/tasks_gen/eval/G0022/generation.json b/tasks_gen/eval/G0022/generation.json new file mode 100644 index 0000000..b06e15a --- /dev/null +++ b/tasks_gen/eval/G0022/generation.json @@ -0,0 +1,43 @@ +{ + "id": "G0022", + "pool": "eval", + "object_type": "CLAS", + "category": "D", + "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": "revalidate", + "by": "harness fix 2026-10-03", + "change": "G6 ignores unknown standard superclass (abaplint); reference in dependency order. Bundle not changed.", + "oracle": 95.0, + "null": 0 + }, + { + "stage": "revalidate", + "by": "harness fix 2026-10-03", + "change": "budget floor: max_activations 12 -> 14 (reference alone needs 13).", + "oracle": 100.0, + "null": 0 + } + ], + "accepted": true +} \ No newline at end of file diff --git a/tasks_gen/eval/G0022/hidden/d01_hidden.clas.abap b/tasks_gen/eval/G0022/hidden/d01_hidden.clas.abap new file mode 100644 index 0000000..d506d82 --- /dev/null +++ b/tasks_gen/eval/G0022/hidden/d01_hidden.clas.abap @@ -0,0 +1,223 @@ +CLASS {{p}}d01_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC + FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. + + PRIVATE SECTION. + TYPES ty_amount TYPE {{p}}if_expense_claim=>ty_amount. + + DATA mo_cut TYPE REF TO {{p}}expense_validator. + + METHODS setup. + METHODS valid_claim_passes FOR TESTING. + METHODS zero_amount_rejected FOR TESTING. + METHODS negative_amount_rejected FOR TESTING. + METHODS unknown_currency_rejected FOR TESTING. + METHODS future_date_rejected FOR TESTING. + METHODS today_is_accepted FOR TESTING. + METHODS receipt_required FOR TESTING. + METHODS receipt_not_required_below FOR TESTING. + METHODS cost_center_required FOR TESTING. + METHODS cost_center_not_required FOR TESTING. + METHODS amount_rule_has_priority FOR TESTING. + METHODS receipt_rule_has_priority FOR TESTING. + + METHODS build_claim + IMPORTING iv_amount TYPE ty_amount + iv_currency TYPE {{p}}if_expense_claim=>ty_currency + iv_date TYPE d + iv_cc TYPE {{p}}if_expense_claim=>ty_cost_center + iv_receipt TYPE abap_bool + RETURNING VALUE(rs_claim) TYPE {{p}}if_expense_claim=>ty_claim. +ENDCLASS. + + +CLASS {{p}}d01_hidden IMPLEMENTATION. + METHOD setup. + mo_cut = NEW {{p}}expense_validator( ). + ENDMETHOD. + + METHOD build_claim. + rs_claim = VALUE #( amount = iv_amount + currency = iv_currency + claim_date = iv_date + cost_center = iv_cc + has_receipt = iv_receipt ). + ENDMETHOD. + + METHOD valid_claim_passes. + DATA(ls_claim) = build_claim( iv_amount = '250.00' iv_currency = 'EUR' + iv_date = sy-datum iv_cc = 'CC1000' + iv_receipt = abap_true ). + TRY. + mo_cut->validate( ls_claim ). + CATCH {{p}}cx_claim_error INTO DATA(lx). + cl_abap_unit_assert=>fail( msg = lx->get_message( ) ). + ENDTRY. + ENDMETHOD. + + METHOD zero_amount_rejected. + DATA(ls_claim) = build_claim( iv_amount = 0 iv_currency = 'EUR' + iv_date = sy-datum iv_cc = 'CC1000' + iv_receipt = abap_true ). + TRY. + mo_cut->validate( ls_claim ). + cl_abap_unit_assert=>fail( msg = 'Exception expected' ). + CATCH {{p}}cx_claim_amount INTO DATA(lx). + cl_abap_unit_assert=>assert_equals( exp = CONV ty_amount( '0.00' ) + act = lx->mv_amount ). + cl_abap_unit_assert=>assert_equals( exp = 'Amount must be positive' + act = lx->get_message( ) ). + CATCH {{p}}cx_claim_error INTO DATA(lx_other). + cl_abap_unit_assert=>fail( msg = lx_other->get_message( ) ). + ENDTRY. + ENDMETHOD. + + METHOD negative_amount_rejected. + DATA(ls_claim) = build_claim( iv_amount = '-50.00' iv_currency = 'EUR' + iv_date = sy-datum iv_cc = 'CC1000' + iv_receipt = abap_true ). + TRY. + mo_cut->validate( ls_claim ). + cl_abap_unit_assert=>fail( msg = 'Exception expected' ). + CATCH {{p}}cx_claim_amount INTO DATA(lx). + cl_abap_unit_assert=>assert_equals( exp = CONV ty_amount( '-50.00' ) + act = lx->mv_amount ). + CATCH {{p}}cx_claim_error INTO DATA(lx_other). + cl_abap_unit_assert=>fail( msg = lx_other->get_message( ) ). + ENDTRY. + ENDMETHOD. + + METHOD unknown_currency_rejected. + DATA(ls_claim) = build_claim( iv_amount = '250.00' iv_currency = 'JPY' + iv_date = sy-datum iv_cc = 'CC1000' + iv_receipt = abap_true ). + TRY. + mo_cut->validate( ls_claim ). + cl_abap_unit_assert=>fail( msg = 'Exception expected' ). + CATCH {{p}}cx_claim_currency INTO DATA(lx). + cl_abap_unit_assert=>assert_equals( exp = 'JPY' + act = lx->mv_currency ). + cl_abap_unit_assert=>assert_equals( exp = 'Currency is not supported' + act = lx->get_message( ) ). + CATCH {{p}}cx_claim_error INTO DATA(lx_other). + cl_abap_unit_assert=>fail( msg = lx_other->get_message( ) ). + ENDTRY. + ENDMETHOD. + + METHOD future_date_rejected. + DATA lv_tomorrow TYPE d. + lv_tomorrow = sy-datum + 1. + DATA(ls_claim) = build_claim( iv_amount = '250.00' iv_currency = 'EUR' + iv_date = lv_tomorrow iv_cc = 'CC1000' + iv_receipt = abap_true ). + TRY. + mo_cut->validate( ls_claim ). + cl_abap_unit_assert=>fail( msg = 'Exception expected' ). + CATCH {{p}}cx_claim_date INTO DATA(lx). + cl_abap_unit_assert=>assert_equals( exp = lv_tomorrow + act = lx->mv_claim_date ). + cl_abap_unit_assert=>assert_equals( exp = 'Claim date is in the future' + act = lx->get_message( ) ). + CATCH {{p}}cx_claim_error INTO DATA(lx_other). + cl_abap_unit_assert=>fail( msg = lx_other->get_message( ) ). + ENDTRY. + ENDMETHOD. + + METHOD today_is_accepted. + DATA(ls_claim) = build_claim( iv_amount = '250.00' iv_currency = 'EUR' + iv_date = sy-datum iv_cc = 'CC1000' + iv_receipt = abap_true ). + TRY. + mo_cut->validate( ls_claim ). + CATCH {{p}}cx_claim_error INTO DATA(lx). + cl_abap_unit_assert=>fail( msg = lx->get_message( ) ). + ENDTRY. + ENDMETHOD. + + METHOD receipt_required. + DATA(ls_claim) = build_claim( iv_amount = '250.00' iv_currency = 'EUR' + iv_date = sy-datum iv_cc = 'CC1000' + iv_receipt = abap_false ). + TRY. + mo_cut->validate( ls_claim ). + cl_abap_unit_assert=>fail( msg = 'Exception expected' ). + CATCH {{p}}cx_claim_receipt INTO DATA(lx). + cl_abap_unit_assert=>assert_equals( exp = CONV ty_amount( '250.00' ) + act = lx->mv_amount ). + cl_abap_unit_assert=>assert_equals( exp = 'Receipt is required' + act = lx->get_message( ) ). + CATCH {{p}}cx_claim_error INTO DATA(lx_other). + cl_abap_unit_assert=>fail( msg = lx_other->get_message( ) ). + ENDTRY. + ENDMETHOD. + + METHOD receipt_not_required_below. + DATA(ls_claim) = build_claim( iv_amount = '99.99' iv_currency = 'EUR' + iv_date = sy-datum iv_cc = 'CC1000' + iv_receipt = abap_false ). + TRY. + mo_cut->validate( ls_claim ). + CATCH {{p}}cx_claim_error INTO DATA(lx). + cl_abap_unit_assert=>fail( msg = lx->get_message( ) ). + ENDTRY. + ENDMETHOD. + + METHOD cost_center_required. + DATA(ls_claim) = build_claim( iv_amount = '1500.00' iv_currency = 'EUR' + iv_date = sy-datum iv_cc = '' + iv_receipt = abap_true ). + TRY. + mo_cut->validate( ls_claim ). + cl_abap_unit_assert=>fail( msg = 'Exception expected' ). + CATCH {{p}}cx_claim_costcenter INTO DATA(lx). + cl_abap_unit_assert=>assert_equals( exp = CONV ty_amount( '1500.00' ) + act = lx->mv_amount ). + cl_abap_unit_assert=>assert_equals( exp = 'Cost center is required' + act = lx->get_message( ) ). + CATCH {{p}}cx_claim_error INTO DATA(lx_other). + cl_abap_unit_assert=>fail( msg = lx_other->get_message( ) ). + ENDTRY. + ENDMETHOD. + + METHOD cost_center_not_required. + DATA(ls_claim) = build_claim( iv_amount = '999.99' iv_currency = 'EUR' + iv_date = sy-datum iv_cc = '' + iv_receipt = abap_true ). + TRY. + mo_cut->validate( ls_claim ). + CATCH {{p}}cx_claim_error INTO DATA(lx). + cl_abap_unit_assert=>fail( msg = lx->get_message( ) ). + ENDTRY. + ENDMETHOD. + + METHOD amount_rule_has_priority. + DATA lv_future TYPE d. + lv_future = sy-datum + 1. + DATA(ls_claim) = build_claim( iv_amount = 0 iv_currency = 'JPY' + iv_date = lv_future iv_cc = '' + iv_receipt = abap_false ). + TRY. + mo_cut->validate( ls_claim ). + cl_abap_unit_assert=>fail( msg = 'Exception expected' ). + CATCH {{p}}cx_claim_amount INTO DATA(lx). + cl_abap_unit_assert=>assert_equals( exp = CONV ty_amount( '0.00' ) + act = lx->mv_amount ). + CATCH {{p}}cx_claim_error INTO DATA(lx_other). + cl_abap_unit_assert=>fail( msg = lx_other->get_message( ) ). + ENDTRY. + ENDMETHOD. + + METHOD receipt_rule_has_priority. + DATA(ls_claim) = build_claim( iv_amount = '1500.00' iv_currency = 'EUR' + iv_date = sy-datum iv_cc = '' + iv_receipt = abap_false ). + TRY. + mo_cut->validate( ls_claim ). + cl_abap_unit_assert=>fail( msg = 'Exception expected' ). + CATCH {{p}}cx_claim_receipt INTO DATA(lx). + cl_abap_unit_assert=>assert_equals( exp = CONV ty_amount( '1500.00' ) + act = lx->mv_amount ). + CATCH {{p}}cx_claim_error INTO DATA(lx_other). + cl_abap_unit_assert=>fail( msg = lx_other->get_message( ) ). + ENDTRY. + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0022/reference/cx_claim_amount.clas.abap b/tasks_gen/eval/G0022/reference/cx_claim_amount.clas.abap new file mode 100644 index 0000000..710f09a --- /dev/null +++ b/tasks_gen/eval/G0022/reference/cx_claim_amount.clas.abap @@ -0,0 +1,20 @@ +CLASS {{p}}cx_claim_amount DEFINITION + PUBLIC + INHERITING FROM {{p}}cx_claim_error + FINAL + CREATE PUBLIC. + + PUBLIC SECTION. + DATA mv_amount TYPE {{p}}if_expense_claim=>ty_amount READ-ONLY. + + METHODS constructor + IMPORTING iv_amount TYPE {{p}}if_expense_claim=>ty_amount. +ENDCLASS. + + +CLASS {{p}}cx_claim_amount IMPLEMENTATION. + METHOD constructor. + super->constructor( iv_message = 'Amount must be positive' ). + mv_amount = iv_amount. + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0022/reference/cx_claim_costcenter.clas.abap b/tasks_gen/eval/G0022/reference/cx_claim_costcenter.clas.abap new file mode 100644 index 0000000..31f7c3d --- /dev/null +++ b/tasks_gen/eval/G0022/reference/cx_claim_costcenter.clas.abap @@ -0,0 +1,20 @@ +CLASS {{p}}cx_claim_costcenter DEFINITION + PUBLIC + INHERITING FROM {{p}}cx_claim_error + FINAL + CREATE PUBLIC. + + PUBLIC SECTION. + DATA mv_amount TYPE {{p}}if_expense_claim=>ty_amount READ-ONLY. + + METHODS constructor + IMPORTING iv_amount TYPE {{p}}if_expense_claim=>ty_amount. +ENDCLASS. + + +CLASS {{p}}cx_claim_costcenter IMPLEMENTATION. + METHOD constructor. + super->constructor( iv_message = 'Cost center is required' ). + mv_amount = iv_amount. + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0022/reference/cx_claim_currency.clas.abap b/tasks_gen/eval/G0022/reference/cx_claim_currency.clas.abap new file mode 100644 index 0000000..72ed7a0 --- /dev/null +++ b/tasks_gen/eval/G0022/reference/cx_claim_currency.clas.abap @@ -0,0 +1,20 @@ +CLASS {{p}}cx_claim_currency DEFINITION + PUBLIC + INHERITING FROM {{p}}cx_claim_error + FINAL + CREATE PUBLIC. + + PUBLIC SECTION. + DATA mv_currency TYPE {{p}}if_expense_claim=>ty_currency READ-ONLY. + + METHODS constructor + IMPORTING iv_currency TYPE {{p}}if_expense_claim=>ty_currency. +ENDCLASS. + + +CLASS {{p}}cx_claim_currency IMPLEMENTATION. + METHOD constructor. + super->constructor( iv_message = 'Currency is not supported' ). + mv_currency = iv_currency. + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0022/reference/cx_claim_date.clas.abap b/tasks_gen/eval/G0022/reference/cx_claim_date.clas.abap new file mode 100644 index 0000000..cb444cc --- /dev/null +++ b/tasks_gen/eval/G0022/reference/cx_claim_date.clas.abap @@ -0,0 +1,20 @@ +CLASS {{p}}cx_claim_date DEFINITION + PUBLIC + INHERITING FROM {{p}}cx_claim_error + FINAL + CREATE PUBLIC. + + PUBLIC SECTION. + DATA mv_claim_date TYPE d READ-ONLY. + + METHODS constructor + IMPORTING iv_claim_date TYPE d. +ENDCLASS. + + +CLASS {{p}}cx_claim_date IMPLEMENTATION. + METHOD constructor. + super->constructor( iv_message = 'Claim date is in the future' ). + mv_claim_date = iv_claim_date. + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0022/reference/cx_claim_receipt.clas.abap b/tasks_gen/eval/G0022/reference/cx_claim_receipt.clas.abap new file mode 100644 index 0000000..232c9da --- /dev/null +++ b/tasks_gen/eval/G0022/reference/cx_claim_receipt.clas.abap @@ -0,0 +1,20 @@ +CLASS {{p}}cx_claim_receipt DEFINITION + PUBLIC + INHERITING FROM {{p}}cx_claim_error + FINAL + CREATE PUBLIC. + + PUBLIC SECTION. + DATA mv_amount TYPE {{p}}if_expense_claim=>ty_amount READ-ONLY. + + METHODS constructor + IMPORTING iv_amount TYPE {{p}}if_expense_claim=>ty_amount. +ENDCLASS. + + +CLASS {{p}}cx_claim_receipt IMPLEMENTATION. + METHOD constructor. + super->constructor( iv_message = 'Receipt is required' ). + mv_amount = iv_amount. + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0022/reference/expense_validator.clas.abap b/tasks_gen/eval/G0022/reference/expense_validator.clas.abap new file mode 100644 index 0000000..fed166e --- /dev/null +++ b/tasks_gen/eval/G0022/reference/expense_validator.clas.abap @@ -0,0 +1,85 @@ +CLASS {{p}}expense_validator DEFINITION + PUBLIC + FINAL + CREATE PUBLIC. + + PUBLIC SECTION. + METHODS validate + IMPORTING is_claim TYPE {{p}}if_expense_claim=>ty_claim + RAISING {{p}}cx_claim_error. + + PRIVATE SECTION. + CONSTANTS: + c_receipt_limit TYPE {{p}}if_expense_claim=>ty_amount VALUE '100.00', + c_cc_limit TYPE {{p}}if_expense_claim=>ty_amount VALUE '1000.00'. + + METHODS check_amount + IMPORTING is_claim TYPE {{p}}if_expense_claim=>ty_claim + RAISING {{p}}cx_claim_error. + METHODS check_currency + IMPORTING is_claim TYPE {{p}}if_expense_claim=>ty_claim + RAISING {{p}}cx_claim_error. + METHODS check_date + IMPORTING is_claim TYPE {{p}}if_expense_claim=>ty_claim + RAISING {{p}}cx_claim_error. + METHODS check_receipt + IMPORTING is_claim TYPE {{p}}if_expense_claim=>ty_claim + RAISING {{p}}cx_claim_error. + METHODS check_cost_center + IMPORTING is_claim TYPE {{p}}if_expense_claim=>ty_claim + RAISING {{p}}cx_claim_error. + METHODS is_supported_currency + IMPORTING iv_currency TYPE {{p}}if_expense_claim=>ty_currency + RETURNING VALUE(rv_supported) TYPE abap_bool. +ENDCLASS. + + +CLASS {{p}}expense_validator IMPLEMENTATION. + METHOD validate. + check_amount( is_claim ). + check_currency( is_claim ). + check_date( is_claim ). + check_receipt( is_claim ). + check_cost_center( is_claim ). + ENDMETHOD. + + METHOD check_amount. + IF is_claim-amount <= 0. + RAISE EXCEPTION TYPE {{p}}cx_claim_amount + EXPORTING iv_amount = is_claim-amount. + ENDIF. + ENDMETHOD. + + METHOD check_currency. + IF is_supported_currency( is_claim-currency ) = abap_false. + RAISE EXCEPTION TYPE {{p}}cx_claim_currency + EXPORTING iv_currency = is_claim-currency. + ENDIF. + ENDMETHOD. + + METHOD check_date. + IF is_claim-claim_date > sy-datum. + RAISE EXCEPTION TYPE {{p}}cx_claim_date + EXPORTING iv_claim_date = is_claim-claim_date. + ENDIF. + ENDMETHOD. + + METHOD check_receipt. + IF is_claim-amount >= c_receipt_limit AND is_claim-has_receipt = abap_false. + RAISE EXCEPTION TYPE {{p}}cx_claim_receipt + EXPORTING iv_amount = is_claim-amount. + ENDIF. + ENDMETHOD. + + METHOD check_cost_center. + IF is_claim-amount >= c_cc_limit AND is_claim-cost_center IS INITIAL. + RAISE EXCEPTION TYPE {{p}}cx_claim_costcenter + EXPORTING iv_amount = is_claim-amount. + ENDIF. + ENDMETHOD. + + METHOD is_supported_currency. + rv_supported = xsdbool( iv_currency = 'EUR' OR iv_currency = 'USD' + OR iv_currency = 'GBP' OR iv_currency = 'CHF' ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0022/reference/expense_validator.testclasses.abap b/tasks_gen/eval/G0022/reference/expense_validator.testclasses.abap new file mode 100644 index 0000000..b1fc8eb --- /dev/null +++ b/tasks_gen/eval/G0022/reference/expense_validator.testclasses.abap @@ -0,0 +1,127 @@ +CLASS ltc_expense_validator DEFINITION FINAL FOR TESTING + DURATION SHORT RISK LEVEL HARMLESS. + + PRIVATE SECTION. + TYPES ty_amount TYPE {{p}}if_expense_claim=>ty_amount. + + DATA mo_cut TYPE REF TO {{p}}expense_validator. + + METHODS setup. + METHODS valid_claim FOR TESTING. + METHODS zero_amount FOR TESTING. + METHODS unknown_currency FOR TESTING. + METHODS future_date FOR TESTING. + METHODS receipt_missing FOR TESTING. + METHODS cost_center_missing FOR TESTING. + + METHODS build_claim + IMPORTING iv_amount TYPE ty_amount + iv_currency TYPE {{p}}if_expense_claim=>ty_currency + iv_date TYPE d + iv_cc TYPE {{p}}if_expense_claim=>ty_cost_center + iv_receipt TYPE abap_bool + RETURNING VALUE(rs_claim) TYPE {{p}}if_expense_claim=>ty_claim. +ENDCLASS. + + +CLASS ltc_expense_validator IMPLEMENTATION. + METHOD setup. + mo_cut = NEW {{p}}expense_validator( ). + ENDMETHOD. + + METHOD build_claim. + rs_claim = VALUE #( amount = iv_amount + currency = iv_currency + claim_date = iv_date + cost_center = iv_cc + has_receipt = iv_receipt ). + ENDMETHOD. + + METHOD valid_claim. + DATA(ls_claim) = build_claim( iv_amount = '250.00' iv_currency = 'EUR' + iv_date = sy-datum iv_cc = 'CC1000' + iv_receipt = abap_true ). + TRY. + mo_cut->validate( ls_claim ). + CATCH {{p}}cx_claim_error INTO DATA(lx). + cl_abap_unit_assert=>fail( msg = lx->get_message( ) ). + ENDTRY. + ENDMETHOD. + + METHOD zero_amount. + DATA(ls_claim) = build_claim( iv_amount = 0 iv_currency = 'EUR' + iv_date = sy-datum iv_cc = 'CC1000' + iv_receipt = abap_true ). + TRY. + mo_cut->validate( ls_claim ). + cl_abap_unit_assert=>fail( msg = 'Exception expected' ). + CATCH {{p}}cx_claim_amount INTO DATA(lx). + cl_abap_unit_assert=>assert_equals( exp = CONV ty_amount( '0.00' ) + act = lx->mv_amount ). + CATCH {{p}}cx_claim_error INTO DATA(lx_other). + cl_abap_unit_assert=>fail( msg = lx_other->get_message( ) ). + ENDTRY. + ENDMETHOD. + + METHOD unknown_currency. + DATA(ls_claim) = build_claim( iv_amount = '250.00' iv_currency = 'JPY' + iv_date = sy-datum iv_cc = 'CC1000' + iv_receipt = abap_true ). + TRY. + mo_cut->validate( ls_claim ). + cl_abap_unit_assert=>fail( msg = 'Exception expected' ). + CATCH {{p}}cx_claim_currency INTO DATA(lx). + cl_abap_unit_assert=>assert_equals( exp = 'JPY' + act = lx->mv_currency ). + CATCH {{p}}cx_claim_error INTO DATA(lx_other). + cl_abap_unit_assert=>fail( msg = lx_other->get_message( ) ). + ENDTRY. + ENDMETHOD. + + METHOD future_date. + DATA lv_future TYPE d. + lv_future = sy-datum + 1. + DATA(ls_claim) = build_claim( iv_amount = '250.00' iv_currency = 'EUR' + iv_date = lv_future iv_cc = 'CC1000' + iv_receipt = abap_true ). + TRY. + mo_cut->validate( ls_claim ). + cl_abap_unit_assert=>fail( msg = 'Exception expected' ). + CATCH {{p}}cx_claim_date INTO DATA(lx). + cl_abap_unit_assert=>assert_equals( exp = lv_future + act = lx->mv_claim_date ). + CATCH {{p}}cx_claim_error INTO DATA(lx_other). + cl_abap_unit_assert=>fail( msg = lx_other->get_message( ) ). + ENDTRY. + ENDMETHOD. + + METHOD receipt_missing. + DATA(ls_claim) = build_claim( iv_amount = '250.00' iv_currency = 'EUR' + iv_date = sy-datum iv_cc = 'CC1000' + iv_receipt = abap_false ). + TRY. + mo_cut->validate( ls_claim ). + cl_abap_unit_assert=>fail( msg = 'Exception expected' ). + CATCH {{p}}cx_claim_receipt INTO DATA(lx). + cl_abap_unit_assert=>assert_equals( exp = 'Receipt is required' + act = lx->get_message( ) ). + CATCH {{p}}cx_claim_error INTO DATA(lx_other). + cl_abap_unit_assert=>fail( msg = lx_other->get_message( ) ). + ENDTRY. + ENDMETHOD. + + METHOD cost_center_missing. + DATA(ls_claim) = build_claim( iv_amount = '1500.00' iv_currency = 'EUR' + iv_date = sy-datum iv_cc = '' + iv_receipt = abap_true ). + TRY. + mo_cut->validate( ls_claim ). + cl_abap_unit_assert=>fail( msg = 'Exception expected' ). + CATCH {{p}}cx_claim_costcenter INTO DATA(lx). + cl_abap_unit_assert=>assert_equals( exp = 'Cost center is required' + act = lx->get_message( ) ). + CATCH {{p}}cx_claim_error INTO DATA(lx_other). + cl_abap_unit_assert=>fail( msg = lx_other->get_message( ) ). + ENDTRY. + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0022/seed/cx_claim_error.clas.abap b/tasks_gen/eval/G0022/seed/cx_claim_error.clas.abap new file mode 100644 index 0000000..874c5be --- /dev/null +++ b/tasks_gen/eval/G0022/seed/cx_claim_error.clas.abap @@ -0,0 +1,27 @@ +CLASS {{p}}cx_claim_error DEFINITION + PUBLIC + INHERITING FROM cx_static_check + CREATE PUBLIC. + + PUBLIC SECTION. + METHODS constructor + IMPORTING iv_message TYPE string. + + METHODS get_message + RETURNING VALUE(rv_message) TYPE string. + + PRIVATE SECTION. + DATA mv_message TYPE string. +ENDCLASS. + + +CLASS {{p}}cx_claim_error IMPLEMENTATION. + METHOD constructor. + super->constructor( ). + mv_message = iv_message. + ENDMETHOD. + + METHOD get_message. + rv_message = mv_message. + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0022/seed/if_expense_claim.intf.abap b/tasks_gen/eval/G0022/seed/if_expense_claim.intf.abap new file mode 100644 index 0000000..5d8b1c5 --- /dev/null +++ b/tasks_gen/eval/G0022/seed/if_expense_claim.intf.abap @@ -0,0 +1,14 @@ +INTERFACE {{p}}if_expense_claim PUBLIC. + TYPES ty_amount TYPE p LENGTH 8 DECIMALS 2. + TYPES ty_currency TYPE c LENGTH 3. + TYPES ty_cost_center TYPE c LENGTH 10. + + TYPES: + BEGIN OF ty_claim, + amount TYPE ty_amount, + currency TYPE ty_currency, + claim_date TYPE d, + cost_center TYPE ty_cost_center, + has_receipt TYPE abap_bool, + END OF ty_claim. +ENDINTERFACE. diff --git a/tasks_gen/eval/G0022/spec.md b/tasks_gen/eval/G0022/spec.md new file mode 100644 index 0000000..2a47652 --- /dev/null +++ b/tasks_gen/eval/G0022/spec.md @@ -0,0 +1,72 @@ +# 1. Goal +Validate an employee expense claim before it is posted to accounting. The check +reports the first rule that fails as a class-based exception. Each exception +carries the value that caused the failure and a short message. + +# 2. Open questions +None. + +# 3. Context +- The interface {{P}}IF_EXPENSE_CLAIM exists in package $TMP and is active. It + contains types only and has no methods: + - TY_AMOUNT, a packed number with length 8 and 2 decimals. + - TY_CURRENCY, a character field with length 3. + - TY_COST_CENTER, a character field with length 10. + - TY_CLAIM, a structure with the components AMOUNT (TY_AMOUNT), CURRENCY + (TY_CURRENCY), CLAIM_DATE (type D), COST_CENTER (TY_COST_CENTER) and + HAS_RECEIPT (ABAP_BOOL). +- The class {{P}}CX_CLAIM_ERROR exists in package $TMP and is active. It is a + direct subclass of CX_STATIC_CHECK. Its constructor has the importing + parameter IV_MESSAGE (TYPE string). The method GET_MESSAGE returns this text + (TYPE string). + +# 4. Contract +- Create the class {{P}}EXPENSE_VALIDATOR in package $TMP. +- The class is public and final and has a public constructor without parameters. +- The class has exactly one public method: + VALIDATE IMPORTING is_claim TYPE {{p}}if_expense_claim=>ty_claim + RAISING {{p}}cx_claim_error. +- Create these exception classes in package $TMP. Each one inherits from + {{P}}CX_CLAIM_ERROR, is final and has a public constructor. + - {{P}}CX_CLAIM_AMOUNT: constructor parameter IV_AMOUNT of type TY_AMOUNT of + {{P}}IF_EXPENSE_CLAIM. Public read-only attribute MV_AMOUNT of the same + type. Message: Amount must be positive + - {{P}}CX_CLAIM_CURRENCY: constructor parameter IV_CURRENCY of type + TY_CURRENCY. Public read-only attribute MV_CURRENCY of the same type. + Message: Currency is not supported + - {{P}}CX_CLAIM_DATE: constructor parameter IV_CLAIM_DATE of type D. Public + read-only attribute MV_CLAIM_DATE of type D. Message: Claim date is in the + future + - {{P}}CX_CLAIM_RECEIPT: constructor parameter IV_AMOUNT of type TY_AMOUNT. + Public read-only attribute MV_AMOUNT of the same type. Message: Receipt is + required + - {{P}}CX_CLAIM_COSTCENTER: constructor parameter IV_AMOUNT of type TY_AMOUNT. + Public read-only attribute MV_AMOUNT of the same type. Message: Cost center + is required +- GET_MESSAGE returns the message of the exception. +- Do not add other public methods or attributes. + +# 5. Business rules +Check the claim in this sequence. Raise the exception of the first rule that +fails. If no rule fails, the method ends without an exception. +1. AMOUNT must be greater than zero. Else raise {{P}}CX_CLAIM_AMOUNT with + IV_AMOUNT = AMOUNT. +2. CURRENCY must be one of EUR, USD, GBP, CHF. Else raise + {{P}}CX_CLAIM_CURRENCY with IV_CURRENCY = CURRENCY. +3. CLAIM_DATE must not be later than the current date. Else raise + {{P}}CX_CLAIM_DATE with IV_CLAIM_DATE = CLAIM_DATE. +4. If AMOUNT is 100.00 or more, HAS_RECEIPT must be true. Else raise + {{P}}CX_CLAIM_RECEIPT with IV_AMOUNT = AMOUNT. +5. If AMOUNT is 1000.00 or more, COST_CENTER must not be initial. Else raise + {{P}}CX_CLAIM_COSTCENTER with IV_AMOUNT = AMOUNT. + +# 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 {{P}}IF_EXPENSE_CLAIM and {{P}}CX_CLAIM_ERROR. + +# 7. Acceptance +- All objects are active and have no syntax error. +- The hidden tests pass. +- Write your own ABAP Unit tests for {{P}}EXPENSE_VALIDATOR. diff --git a/tasks_gen/eval/G0022/task.json b/tasks_gen/eval/G0022/task.json new file mode 100644 index 0000000..8164293 --- /dev/null +++ b/tasks_gen/eval/G0022/task.json @@ -0,0 +1,107 @@ +{ + "id": "G0022", + "category": "D", + "object_type": "CLAS", + "difficulty": 2, + "release_target": "v816", + "expected_outcome": "implement", + "budget": { + "max_tool_calls": 45, + "max_activations": 14 + }, + "seed": [ + { + "type": "INTF", + "name": "{{P}}IF_EXPENSE_CLAIM", + "file": "seed/if_expense_claim.intf.abap", + "description": "Types for an expense claim" + }, + { + "type": "CLAS", + "name": "{{P}}CX_CLAIM_ERROR", + "file": "seed/cx_claim_error.clas.abap", + "description": "Base exception that carries a message text" + } + ], + "contract": [ + { + "type": "CLAS", + "name": "{{P}}CX_CLAIM_AMOUNT" + }, + { + "type": "CLAS", + "name": "{{P}}CX_CLAIM_CURRENCY" + }, + { + "type": "CLAS", + "name": "{{P}}CX_CLAIM_DATE" + }, + { + "type": "CLAS", + "name": "{{P}}CX_CLAIM_RECEIPT" + }, + { + "type": "CLAS", + "name": "{{P}}CX_CLAIM_COSTCENTER" + }, + { + "type": "CLAS", + "name": "{{P}}EXPENSE_VALIDATOR" + } + ], + "out_of_scope": [ + "{{P}}IF_EXPENSE_CLAIM", + "{{P}}CX_CLAIM_ERROR" + ], + "hidden_tests": [ + { + "type": "CLAS", + "name": "{{P}}D01_HIDDEN", + "file": "hidden/d01_hidden.clas.abap", + "description": "Hidden tests for the expense claim validation" + } + ], + "reference": [ + { + "type": "CLAS", + "name": "{{P}}CX_CLAIM_AMOUNT", + "file": "reference/cx_claim_amount.clas.abap", + "description": "Exception for a non positive amount" + }, + { + "type": "CLAS", + "name": "{{P}}CX_CLAIM_CURRENCY", + "file": "reference/cx_claim_currency.clas.abap", + "description": "Exception for an unsupported currency" + }, + { + "type": "CLAS", + "name": "{{P}}CX_CLAIM_DATE", + "file": "reference/cx_claim_date.clas.abap", + "description": "Exception for a claim date in the future" + }, + { + "type": "CLAS", + "name": "{{P}}CX_CLAIM_RECEIPT", + "file": "reference/cx_claim_receipt.clas.abap", + "description": "Exception for a missing receipt" + }, + { + "type": "CLAS", + "name": "{{P}}CX_CLAIM_COSTCENTER", + "file": "reference/cx_claim_costcenter.clas.abap", + "description": "Exception for a missing cost center" + }, + { + "type": "CLAS", + "name": "{{P}}EXPENSE_VALIDATOR", + "file": "reference/expense_validator.clas.abap", + "description": "Validates an expense claim and raises class-based exceptions", + "testclasses_file": "reference/expense_validator.testclasses.abap" + } + ], + "craft_checks": [ + "method_length", + "exception_design" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0023/generation.json b/tasks_gen/eval/G0023/generation.json new file mode 100644 index 0000000..ca80d19 --- /dev/null +++ b/tasks_gen/eval/G0023/generation.json @@ -0,0 +1,14 @@ +{ + "id": "G0023", + "pool": "eval", + "object_type": "CLAS", + "category": "E", + "attempts": [ + { + "stage": "validate", + "oracle": 100.0, + "null": 0 + } + ], + "accepted": true +} \ No newline at end of file diff --git a/tasks_gen/eval/G0023/hidden/e01_hidden.clas.abap b/tasks_gen/eval/G0023/hidden/e01_hidden.clas.abap new file mode 100644 index 0000000..5f24a5c --- /dev/null +++ b/tasks_gen/eval/G0023/hidden/e01_hidden.clas.abap @@ -0,0 +1,73 @@ +CLASS {{p}}e01_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_cost + IMPORTING iv_zone TYPE {{p}}if_freight_calc=>ty_zone + iv_weight TYPE {{p}}if_freight_calc=>ty_weight + iv_exp TYPE {{p}}if_freight_calc=>ty_amount. + METHODS unknown_zone_is_zero FOR TESTING. + METHODS minimum_applies_after_charge FOR TESTING. + METHODS weight_charge_rounds_half_up FOR TESTING. + METHODS all_zones_are_rated FOR TESTING. + METHODS surcharge_uses_minimum FOR TESTING. + METHODS heavy_discount_on_total FOR TESTING. + METHODS no_discount_at_exactly_1000 FOR TESTING. + METHODS discount_rounds_half_up FOR TESTING. + METHODS small_weight_uses_minimum FOR TESTING. +ENDCLASS. + + +CLASS {{p}}e01_hidden IMPLEMENTATION. + METHOD setup. + mo_cut = NEW {{p}}freight_calculator( ). + ENDMETHOD. + + METHOD assert_cost. + cl_abap_unit_assert=>assert_equals( + exp = iv_exp + act = mo_cut->calculate( iv_zone = iv_zone iv_weight = iv_weight ) + msg = |zone { iv_zone }, weight { iv_weight }| ). + ENDMETHOD. + + METHOD unknown_zone_is_zero. + assert_cost( iv_zone = 'XX' iv_weight = '500.000' iv_exp = '0.00' ). + ENDMETHOD. + + METHOD minimum_applies_after_charge. + assert_cost( iv_zone = 'DE' iv_weight = '1.000' iv_exp = '26.63' ). + ENDMETHOD. + + METHOD weight_charge_rounds_half_up. + assert_cost( iv_zone = 'CN' iv_weight = '20.004' iv_exp = '54.76' ). + ENDMETHOD. + + METHOD all_zones_are_rated. + assert_cost( iv_zone = 'DE' iv_weight = '100.000' iv_exp = '103.84' ). + assert_cost( iv_zone = 'FR' iv_weight = '20.000' iv_exp = '39.78' ). + assert_cost( iv_zone = 'NL' iv_weight = '50.000' iv_exp = '50.11' ). + assert_cost( iv_zone = 'US' iv_weight = '50.000' iv_exp = '97.65' ). + assert_cost( iv_zone = 'CN' iv_weight = '100.000' iv_exp = '164.25' ). + ENDMETHOD. + + METHOD surcharge_uses_minimum. + assert_cost( iv_zone = 'NL' iv_weight = '0.000' iv_exp = '21.10' ). + ENDMETHOD. + + METHOD heavy_discount_on_total. + assert_cost( iv_zone = 'DE' iv_weight = '1500.000' iv_exp = '1302.63' ). + ENDMETHOD. + + METHOD no_discount_at_exactly_1000. + assert_cost( iv_zone = 'DE' iv_weight = '1000.000' iv_exp = '918.56' ). + ENDMETHOD. + + METHOD discount_rounds_half_up. + assert_cost( iv_zone = 'DE' iv_weight = '1090.070' iv_exp = '950.10' ). + ENDMETHOD. + + METHOD small_weight_uses_minimum. + assert_cost( iv_zone = 'FR' iv_weight = '5.000' iv_exp = '32.25' ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0023/reference/freight_calculator.clas.abap b/tasks_gen/eval/G0023/reference/freight_calculator.clas.abap new file mode 100644 index 0000000..817d896 --- /dev/null +++ b/tasks_gen/eval/G0023/reference/freight_calculator.clas.abap @@ -0,0 +1,62 @@ +CLASS {{p}}freight_calculator DEFINITION PUBLIC FINAL CREATE PUBLIC. + PUBLIC SECTION. + INTERFACES {{p}}if_freight_calc. + + PRIVATE SECTION. + TYPES: + BEGIN OF ty_rate, + zone TYPE {{p}}if_freight_calc=>ty_zone, + base TYPE {{p}}if_freight_calc=>ty_amount, + per_kg TYPE {{p}}if_freight_calc=>ty_amount, + minimum TYPE {{p}}if_freight_calc=>ty_amount, + surcharge TYPE p LENGTH 4 DECIMALS 3, + END OF ty_rate, + tt_rate TYPE SORTED TABLE OF ty_rate WITH UNIQUE KEY zone. + + CONSTANTS: + c_heavy_limit TYPE {{p}}if_freight_calc=>ty_weight VALUE '1000.000', + c_discount TYPE p LENGTH 3 DECIMALS 2 VALUE '0.95'. + + METHODS rates RETURNING VALUE(rt_rate) TYPE tt_rate. +ENDCLASS. + + +CLASS {{p}}freight_calculator IMPLEMENTATION. + METHOD {{p}}if_freight_calc~calculate. + DATA(lt_rate) = rates( ). + READ TABLE lt_rate INTO DATA(ls_rate) WITH TABLE KEY zone = iv_zone. + IF sy-subrc <> 0. + rv_cost = 0. + RETURN. + ENDIF. + + DATA lv_charge TYPE {{p}}if_freight_calc=>ty_amount. + lv_charge = ls_rate-per_kg * iv_weight. + + DATA lv_freight TYPE {{p}}if_freight_calc=>ty_amount. + lv_freight = ls_rate-base + lv_charge. + IF lv_freight < ls_rate-minimum. + lv_freight = ls_rate-minimum. + ENDIF. + + DATA lv_surcharge TYPE {{p}}if_freight_calc=>ty_amount. + lv_surcharge = lv_freight * ls_rate-surcharge. + + DATA lv_total TYPE {{p}}if_freight_calc=>ty_amount. + lv_total = lv_freight + lv_surcharge. + IF iv_weight > c_heavy_limit. + lv_total = lv_total * c_discount. + ENDIF. + + rv_cost = lv_total. + ENDMETHOD. + + METHOD rates. + rt_rate = VALUE #( + ( zone = 'DE' base = '12.50' per_kg = '0.85' minimum = '25.00' surcharge = '0.065' ) + ( zone = 'FR' base = '15.00' per_kg = '1.10' minimum = '30.00' surcharge = '0.075' ) + ( zone = 'NL' base = '10.00' per_kg = '0.75' minimum = '20.00' surcharge = '0.055' ) + ( zone = 'US' base = '20.00' per_kg = '1.40' minimum = '45.00' surcharge = '0.085' ) + ( zone = 'CN' base = '25.00' per_kg = '1.25' minimum = '50.00' surcharge = '0.095' ) ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0023/reference/freight_calculator.testclasses.abap b/tasks_gen/eval/G0023/reference/freight_calculator.testclasses.abap new file mode 100644 index 0000000..8053918 --- /dev/null +++ b/tasks_gen/eval/G0023/reference/freight_calculator.testclasses.abap @@ -0,0 +1,75 @@ +CLASS ltc_freight_calculator DEFINITION FINAL FOR TESTING + DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + DATA mo_cut TYPE REF TO {{p}}if_freight_calc. + METHODS setup. + METHODS unknown_zone FOR TESTING. + METHODS minimum_charge FOR TESTING. + METHODS all_zones FOR TESTING. + METHODS heavy_discount FOR TESTING. + METHODS no_discount_at_1000 FOR TESTING. + METHODS rounding_half_up FOR TESTING. +ENDCLASS. + + +CLASS ltc_freight_calculator IMPLEMENTATION. + METHOD setup. + mo_cut = NEW {{p}}freight_calculator( ). + ENDMETHOD. + + METHOD unknown_zone. + DATA lv_exp TYPE {{p}}if_freight_calc=>ty_amount. + lv_exp = '0.00'. + cl_abap_unit_assert=>assert_equals( + exp = lv_exp + act = mo_cut->calculate( iv_zone = 'XX' iv_weight = '10.000' ) ). + ENDMETHOD. + + METHOD minimum_charge. + DATA lv_exp TYPE {{p}}if_freight_calc=>ty_amount. + lv_exp = '26.63'. + cl_abap_unit_assert=>assert_equals( + exp = lv_exp + act = mo_cut->calculate( iv_zone = 'DE' iv_weight = '1.000' ) ). + ENDMETHOD. + + METHOD all_zones. + DATA lv_exp TYPE {{p}}if_freight_calc=>ty_amount. + lv_exp = '103.84'. + cl_abap_unit_assert=>assert_equals( + exp = lv_exp + act = mo_cut->calculate( iv_zone = 'DE' iv_weight = '100.000' ) msg = `DE` ). + lv_exp = '39.78'. + cl_abap_unit_assert=>assert_equals( + exp = lv_exp + act = mo_cut->calculate( iv_zone = 'FR' iv_weight = '20.000' ) msg = `FR` ). + lv_exp = '164.25'. + cl_abap_unit_assert=>assert_equals( + exp = lv_exp + act = mo_cut->calculate( iv_zone = 'CN' iv_weight = '100.000' ) msg = `CN` ). + ENDMETHOD. + + METHOD heavy_discount. + DATA lv_exp TYPE {{p}}if_freight_calc=>ty_amount. + lv_exp = '1302.63'. + cl_abap_unit_assert=>assert_equals( + exp = lv_exp + act = mo_cut->calculate( iv_zone = 'DE' iv_weight = '1500.000' ) ). + ENDMETHOD. + + METHOD no_discount_at_1000. + DATA lv_exp TYPE {{p}}if_freight_calc=>ty_amount. + lv_exp = '918.56'. + cl_abap_unit_assert=>assert_equals( + exp = lv_exp + act = mo_cut->calculate( iv_zone = 'DE' iv_weight = '1000.000' ) ). + ENDMETHOD. + + METHOD rounding_half_up. + DATA lv_exp TYPE {{p}}if_freight_calc=>ty_amount. + lv_exp = '54.76'. + cl_abap_unit_assert=>assert_equals( + exp = lv_exp + act = mo_cut->calculate( iv_zone = 'CN' iv_weight = '20.004' ) ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0023/seed/freight_legacy.clas.abap b/tasks_gen/eval/G0023/seed/freight_legacy.clas.abap new file mode 100644 index 0000000..a9803b2 --- /dev/null +++ b/tasks_gen/eval/G0023/seed/freight_legacy.clas.abap @@ -0,0 +1,61 @@ +CLASS {{p}}freight_legacy DEFINITION PUBLIC FINAL CREATE PUBLIC. + PUBLIC SECTION. + INTERFACES {{p}}if_freight_calc. +ENDCLASS. + + +CLASS {{p}}freight_legacy IMPLEMENTATION. + METHOD {{p}}if_freight_calc~calculate. + DATA: lv_base TYPE {{p}}if_freight_calc=>ty_amount, + lv_per_kg TYPE {{p}}if_freight_calc=>ty_amount, + lv_minimum TYPE {{p}}if_freight_calc=>ty_amount, + lv_surcharge TYPE p LENGTH 4 DECIMALS 3, + lv_charge TYPE {{p}}if_freight_calc=>ty_amount, + lv_freight TYPE {{p}}if_freight_calc=>ty_amount, + lv_fee TYPE {{p}}if_freight_calc=>ty_amount, + lv_total TYPE {{p}}if_freight_calc=>ty_amount. + + CASE iv_zone. + WHEN 'DE'. + lv_base = '12.50'. + lv_per_kg = '0.85'. + lv_minimum = '25.00'. + lv_surcharge = '0.065'. + WHEN 'FR'. + lv_base = '15.00'. + lv_per_kg = '1.10'. + lv_minimum = '30.00'. + lv_surcharge = '0.075'. + WHEN 'NL'. + lv_base = '10.00'. + lv_per_kg = '0.75'. + lv_minimum = '20.00'. + lv_surcharge = '0.055'. + WHEN 'US'. + lv_base = '20.00'. + lv_per_kg = '1.40'. + lv_minimum = '45.00'. + lv_surcharge = '0.085'. + WHEN 'CN'. + lv_base = '25.00'. + lv_per_kg = '1.25'. + lv_minimum = '50.00'. + lv_surcharge = '0.095'. + WHEN OTHERS. + rv_cost = 0. + RETURN. + ENDCASE. + + lv_charge = lv_per_kg * iv_weight. + lv_freight = lv_base + lv_charge. + IF lv_freight < lv_minimum. + lv_freight = lv_minimum. + ENDIF. + lv_fee = lv_freight * lv_surcharge. + lv_total = lv_freight + lv_fee. + IF iv_weight > '1000.000'. + lv_total = lv_total * '0.95'. + ENDIF. + rv_cost = lv_total. + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0023/seed/if_freight_calc.intf.abap b/tasks_gen/eval/G0023/seed/if_freight_calc.intf.abap new file mode 100644 index 0000000..aa037ac --- /dev/null +++ b/tasks_gen/eval/G0023/seed/if_freight_calc.intf.abap @@ -0,0 +1,11 @@ +INTERFACE {{p}}if_freight_calc PUBLIC. + TYPES: + ty_zone TYPE c LENGTH 2, + ty_weight TYPE p LENGTH 9 DECIMALS 3, + ty_amount TYPE p LENGTH 7 DECIMALS 2. + + METHODS calculate + IMPORTING iv_zone TYPE ty_zone + iv_weight TYPE ty_weight + RETURNING VALUE(rv_cost) TYPE ty_amount. +ENDINTERFACE. diff --git a/tasks_gen/eval/G0023/spec.md b/tasks_gen/eval/G0023/spec.md new file mode 100644 index 0000000..5e6b4fd --- /dev/null +++ b/tasks_gen/eval/G0023/spec.md @@ -0,0 +1,69 @@ +# 1. Goal + +A freight forwarder calculates the freight cost of a shipment. A legacy class +calculates this cost today. Write the calculation as a clean class. The cost of +every shipment must stay the same. + +# 2. Open questions +None. + +# 3. Context +- The interface {{P}}IF_FREIGHT_CALC exists in package $TMP and is active. +- The legacy class {{P}}FREIGHT_LEGACY exists in package $TMP and is active. It + implements {{P}}IF_FREIGHT_CALC. Its results are the reference for the new class. +- The interface declares the types TY_ZONE (character, length 2), TY_WEIGHT + (packed number, length 9, 3 decimal places) and TY_AMOUNT (packed number, + length 7, 2 decimal places), and the method CALCULATE. + +# 4. Contract +- Create the class {{P}}FREIGHT_CALCULATOR 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 packed numbers with 2 decimal places. Round an amount to +2 decimal places before you use it in the next step. Round half away from zero. + +1. Zone + The zone is a two-character code. These zones exist: + + zone base per kg minimum surcharge factor + DE 12.50 0.85 25.00 0.065 + FR 15.00 1.10 30.00 0.075 + NL 10.00 0.75 20.00 0.055 + US 20.00 1.40 45.00 0.085 + CN 25.00 1.25 50.00 0.095 + + If the zone is not one of these, the result is 0.00 and no other rule applies. + +2. Weight charge + weight charge = weight * per kg of the zone, rounded to 2 decimal places. + +3. Freight + freight = base of the zone + weight charge. + If freight is less than the minimum of the zone, freight is the minimum. + +4. Surcharge + surcharge = freight * surcharge factor of the zone, rounded to 2 decimal places. + +5. Total + total = freight + surcharge. + +6. Heavy shipment + If the weight is greater than 1000.000 kg, total = total * 0.95, rounded to + 2 decimal places. + +7. The result is total. + +# 6. Constraints +- Release target: SAP_BASIS 816 (ABAP Platform 2025). +- 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 and {{P}}FREIGHT_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/G0023/task.json b/tasks_gen/eval/G0023/task.json new file mode 100644 index 0000000..b99dd91 --- /dev/null +++ b/tasks_gen/eval/G0023/task.json @@ -0,0 +1,58 @@ +{ + "id": "G0023", + "category": "E", + "object_type": "CLAS", + "difficulty": 2, + "release_target": "v816", + "expected_outcome": "implement", + "budget": { + "max_tool_calls": 60, + "max_activations": 15 + }, + "seed": [ + { + "type": "INTF", + "name": "{{P}}IF_FREIGHT_CALC", + "file": "seed/if_freight_calc.intf.abap", + "description": "Freight calculation contract with packed number types" + }, + { + "type": "CLAS", + "name": "{{P}}FREIGHT_LEGACY", + "file": "seed/freight_legacy.clas.abap", + "description": "Legacy freight calculator with packed arithmetic and a CASE over zones" + } + ], + "contract": [ + { + "type": "CLAS", + "name": "{{P}}FREIGHT_CALCULATOR", + "implements": "{{P}}IF_FREIGHT_CALC" + } + ], + "out_of_scope": [ + "{{P}}IF_FREIGHT_CALC", + "{{P}}FREIGHT_LEGACY" + ], + "hidden_tests": [ + { + "type": "CLAS", + "name": "{{P}}E01_HIDDEN", + "file": "hidden/e01_hidden.clas.abap", + "description": "E01 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 a rate table", + "testclasses_file": "reference/freight_calculator.testclasses.abap" + } + ], + "craft_checks": [ + "method_length", + "no_global_variables" + ] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0024/generation.json b/tasks_gen/eval/G0024/generation.json new file mode 100644 index 0000000..5a2051a --- /dev/null +++ b/tasks_gen/eval/G0024/generation.json @@ -0,0 +1,19 @@ +{ + "id": "G0024", + "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/G0024/hidden/t16_hidden.clas.abap b/tasks_gen/eval/G0024/hidden/t16_hidden.clas.abap new file mode 100644 index 0000000..346e34d --- /dev/null +++ b/tasks_gen/eval/G0024/hidden/t16_hidden.clas.abap @@ -0,0 +1,89 @@ +CLASS {{p}}t16_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC + FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + CONSTANTS c_from TYPE d VALUE '20240101'. + CONSTANTS c_to TYPE d VALUE '20240131'. + CLASS-DATA go_env TYPE REF TO if_cds_test_environment. + CLASS-METHODS class_setup. + CLASS-METHODS class_teardown. + METHODS setup. + METHODS row_per_product FOR TESTING. + METHODS sums_quantity FOR TESTING. + METHODS sums_amount FOR TESTING. + METHODS excludes_before_from FOR TESTING. + METHODS excludes_after_to FOR TESTING. + METHODS bounds_inclusive FOR TESTING. + METHODS empty_period FOR TESTING. +ENDCLASS. + + +CLASS {{p}}t16_hidden IMPLEMENTATION. + METHOD class_setup. + go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_SALES_REPORT' ). + ENDMETHOD. + + METHOD class_teardown. + go_env->destroy( ). + ENDMETHOD. + + METHOD setup. + DATA lt_sales TYPE STANDARD TABLE OF {{p}}sales WITH EMPTY KEY. + go_env->clear_doubles( ). + lt_sales = VALUE #( + ( doc_id = 'D1' item_no = 1 product = 'MAT_A' quantity = 3 price = '10.00' sold_on = '20240110' ) + ( doc_id = 'D1' item_no = 2 product = 'MAT_A' quantity = 2 price = '5.00' sold_on = '20240115' ) + ( doc_id = 'D2' item_no = 1 product = 'MAT_B' quantity = 1 price = '100.00' sold_on = '20240131' ) + ( doc_id = 'D3' item_no = 1 product = 'MAT_B' quantity = 4 price = '2.50' sold_on = '20231231' ) + ( doc_id = 'D4' item_no = 1 product = 'MAT_C' quantity = 7 price = '1.00' sold_on = '20240201' ) ). + go_env->insert_test_data( lt_sales ). + ENDMETHOD. + + METHOD row_per_product. + SELECT COUNT(*) FROM {{p}}i_sales_report( p_from = @c_from, p_to = @c_to ) + INTO @DATA(lv_rows). + cl_abap_unit_assert=>assert_equals( exp = 2 act = lv_rows ). + ENDMETHOD. + + METHOD sums_quantity. + SELECT SINGLE total_qty FROM {{p}}i_sales_report( p_from = @c_from, p_to = @c_to ) + WHERE product = 'MAT_A' INTO @DATA(lv_qty). + cl_abap_unit_assert=>assert_equals( exp = 5 act = lv_qty ). + ENDMETHOD. + + METHOD sums_amount. + SELECT SINGLE total_amount FROM {{p}}i_sales_report( p_from = @c_from, p_to = @c_to ) + WHERE product = 'MAT_A' INTO @DATA(lv_amount). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '40.00' ) + act = CONV decfloat34( lv_amount ) ). + ENDMETHOD. + + METHOD excludes_before_from. + SELECT SINGLE total_qty FROM {{p}}i_sales_report( p_from = @c_from, p_to = @c_to ) + WHERE product = 'MAT_B' INTO @DATA(lv_qty). + cl_abap_unit_assert=>assert_equals( exp = 1 act = lv_qty ). + ENDMETHOD. + + METHOD excludes_after_to. + SELECT COUNT(*) FROM {{p}}i_sales_report( p_from = @c_from, p_to = @c_to ) + WHERE product = 'MAT_C' INTO @DATA(lv_rows). + cl_abap_unit_assert=>assert_equals( exp = 0 act = lv_rows ). + ENDMETHOD. + + METHOD bounds_inclusive. + DATA lv_day TYPE d. + lv_day = '20240131'. + SELECT SINGLE total_qty FROM {{p}}i_sales_report( p_from = @lv_day, p_to = @lv_day ) + WHERE product = 'MAT_B' INTO @DATA(lv_qty). + cl_abap_unit_assert=>assert_equals( exp = 1 act = lv_qty ). + ENDMETHOD. + + METHOD empty_period. + DATA lv_from TYPE d. + DATA lv_to TYPE d. + lv_from = '20230101'. + lv_to = '20230131'. + SELECT COUNT(*) FROM {{p}}i_sales_report( p_from = @lv_from, p_to = @lv_to ) + INTO @DATA(lv_rows). + cl_abap_unit_assert=>assert_equals( exp = 0 act = lv_rows ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0024/reference/i_sales_report.ddls.asddls b/tasks_gen/eval/G0024/reference/i_sales_report.ddls.asddls new file mode 100644 index 0000000..a0d2c62 --- /dev/null +++ b/tasks_gen/eval/G0024/reference/i_sales_report.ddls.asddls @@ -0,0 +1,15 @@ +@AccessControl.authorizationCheck: #NOT_REQUIRED +@EndUserText.label: 'Sales per product in a period' +define view entity {{P}}I_SALES_REPORT + with parameters + p_from : abap.dats, + p_to : abap.dats + as select from {{p}}sales as sale +{ + key sale.product as product, + sum( sale.quantity ) as total_qty, + sum( cast( sale.quantity as abap.dec( 15, 2 ) ) * sale.price ) as total_amount +} +where sale.sold_on >= $parameters.p_from + and sale.sold_on <= $parameters.p_to +group by sale.product diff --git a/tasks_gen/eval/G0024/reference/t16_test.clas.abap b/tasks_gen/eval/G0024/reference/t16_test.clas.abap new file mode 100644 index 0000000..3916dd8 --- /dev/null +++ b/tasks_gen/eval/G0024/reference/t16_test.clas.abap @@ -0,0 +1,44 @@ +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 period_is_inclusive FOR TESTING. +ENDCLASS. + + +CLASS {{p}}t16_test IMPLEMENTATION. + METHOD class_setup. + go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_SALES_REPORT' ). + ENDMETHOD. + + METHOD class_teardown. + go_env->destroy( ). + ENDMETHOD. + + METHOD setup. + DATA lt_sales TYPE STANDARD TABLE OF {{p}}sales WITH EMPTY KEY. + go_env->clear_doubles( ). + lt_sales = VALUE #( + ( doc_id = 'S1' item_no = 1 product = 'P1' quantity = 2 price = '1.50' sold_on = '20240501' ) + ( doc_id = 'S1' item_no = 2 product = 'P1' quantity = 1 price = '4.00' sold_on = '20240531' ) + ( doc_id = 'S2' item_no = 1 product = 'P2' quantity = 9 price = '0.10' sold_on = '20240601' ) ). + go_env->insert_test_data( lt_sales ). + ENDMETHOD. + + METHOD period_is_inclusive. + DATA lv_from TYPE d. + DATA lv_to TYPE d. + lv_from = '20240501'. + lv_to = '20240531'. + SELECT SINGLE total_qty, total_amount + FROM {{p}}i_sales_report( p_from = @lv_from, p_to = @lv_to ) + WHERE product = 'P1' + INTO @DATA(ls_result). + cl_abap_unit_assert=>assert_equals( exp = 3 act = ls_result-total_qty ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '7.00' ) + act = CONV decfloat34( ls_result-total_amount ) ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0024/seed/sales.tabl.asabap b/tasks_gen/eval/G0024/seed/sales.tabl.asabap new file mode 100644 index 0000000..1378241 --- /dev/null +++ b/tasks_gen/eval/G0024/seed/sales.tabl.asabap @@ -0,0 +1,14 @@ +@EndUserText.label : 'Sales line' +@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE +@AbapCatalog.tableCategory : #TRANSPARENT +@AbapCatalog.deliveryClass : #A +@AbapCatalog.dataMaintenance : #RESTRICTED +define table {{p}}sales { + key client : abap.clnt not null; + key doc_id : abap.char(10) not null; + key item_no : abap.int4 not null; + product : abap.char(10); + quantity : abap.int4; + price : abap.dec(15,2); + sold_on : abap.dats; +} diff --git a/tasks_gen/eval/G0024/seed/seed_sales.clas.abap b/tasks_gen/eval/G0024/seed/seed_sales.clas.abap new file mode 100644 index 0000000..e15ca8a --- /dev/null +++ b/tasks_gen/eval/G0024/seed/seed_sales.clas.abap @@ -0,0 +1,22 @@ +CLASS {{p}}seed_sales DEFINITION PUBLIC FINAL CREATE PUBLIC. + PUBLIC SECTION. + INTERFACES if_oo_adt_classrun. + PROTECTED SECTION. + PRIVATE SECTION. +ENDCLASS. + + +CLASS {{p}}seed_sales IMPLEMENTATION. + METHOD if_oo_adt_classrun~main. + DATA lt_sales TYPE STANDARD TABLE OF {{p}}sales WITH EMPTY KEY. + lt_sales = VALUE #( + ( doc_id = 'D1' item_no = 1 product = 'MAT_A' quantity = 3 price = '10.00' sold_on = '20240110' ) + ( doc_id = 'D1' item_no = 2 product = 'MAT_A' quantity = 2 price = '5.00' sold_on = '20240115' ) + ( doc_id = 'D2' item_no = 1 product = 'MAT_B' quantity = 1 price = '100.00' sold_on = '20240131' ) + ( doc_id = 'D3' item_no = 1 product = 'MAT_B' quantity = 4 price = '2.50' sold_on = '20231231' ) + ( doc_id = 'D4' item_no = 1 product = 'MAT_C' quantity = 7 price = '1.00' sold_on = '20240201' ) ). + DELETE FROM {{p}}sales. + INSERT {{p}}sales FROM TABLE @lt_sales. + out->write( |Seed rows: { lines( lt_sales ) }| ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0024/spec.md b/tasks_gen/eval/G0024/spec.md new file mode 100644 index 0000000..e0bd429 --- /dev/null +++ b/tasks_gen/eval/G0024/spec.md @@ -0,0 +1,40 @@ +# 1. Goal +Give the sales figures of each product for a period. The period is an input of the +view. A report and an OData service read the figures, so the calculation must be in +the database, in a CDS view. + +# 2. Open questions +None. + +# 3. Context +- Table {{P}}SALES (sales line), package $TMP. Key: DOC_ID (CHAR 10), ITEM_NO (INT4). + Fields: PRODUCT (CHAR 10), QUANTITY (INT4), PRICE (DEC 15,2, the price of one + unit), SOLD_ON (DATS, the date of the sale). +- The class {{P}}SEED_SALES fills {{P}}SALES with sample data. Run it if you want to + test the view manually. + +# 4. Contract +- Create the CDS view entity {{P}}I_SALES_REPORT in package $TMP. +- Input parameters, with these names and types: P_FROM (DATS), P_TO (DATS). + The parameters have no default value. +- Elements, with these names: PRODUCT (key), TOTAL_QTY, TOTAL_AMOUNT. +- No authorization check (#NOT_REQUIRED). + +# 5. Business rules +1. A sales line is in the period when SOLD_ON is not before P_FROM and not after + P_TO. P_FROM and P_TO are part of the period. +2. Show one row for each product with at least one sales line in the period. Do not + show a product without a sales line in the period. +3. TOTAL_QTY is the sum of QUANTITY of all sales lines of the product in the period. +4. TOTAL_AMOUNT is the sum of QUANTITY multiplied by PRICE of all sales lines of the + product in the period, with 2 decimals. + +# 6. Constraints +- Release target: 7.58. +- Coding standards: Clean ABAP. +- Out of scope: do not change {{P}}SALES and {{P}}SEED_SALES. + +# 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/G0024/task.json b/tasks_gen/eval/G0024/task.json new file mode 100644 index 0000000..998f9ba --- /dev/null +++ b/tasks_gen/eval/G0024/task.json @@ -0,0 +1,75 @@ +{ + "id": "G0024", + "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}}SALES", + "file": "seed/sales.tabl.asabap", + "description": "Sales line" + }, + { + "type": "CLAS", + "name": "{{P}}SEED_SALES", + "file": "seed/seed_sales.clas.abap", + "run": true, + "description": "Seed data for {{P}}SALES" + } + ], + "contract": [ + { + "type": "DDLS", + "name": "{{P}}I_SALES_REPORT", + "params": [ + { + "name": "P_FROM", + "type": "abap.dats" + }, + { + "name": "P_TO", + "type": "abap.dats" + } + ], + "fields": [ + "PRODUCT", + "TOTAL_QTY", + "TOTAL_AMOUNT" + ] + } + ], + "out_of_scope": [ + "{{P}}SALES", + "{{P}}SEED_SALES" + ], + "hidden_tests": [ + { + "type": "CLAS", + "name": "{{P}}T16_HIDDEN", + "file": "hidden/t16_hidden.clas.abap", + "description": "T16 hidden tests" + } + ], + "reference": [ + { + "type": "DDLS", + "name": "{{P}}I_SALES_REPORT", + "file": "reference/i_sales_report.ddls.asddls", + "description": "Sales figures per product in a period" + }, + { + "type": "CLAS", + "name": "{{P}}T16_TEST", + "file": "reference/t16_test.clas.abap", + "description": "T16 own tests" + } + ], + "craft_checks": [] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0025/generation.json b/tasks_gen/eval/G0025/generation.json new file mode 100644 index 0000000..2a1db6e --- /dev/null +++ b/tasks_gen/eval/G0025/generation.json @@ -0,0 +1,19 @@ +{ + "id": "G0025", + "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/G0025/hidden/t16_hidden.clas.abap b/tasks_gen/eval/G0025/hidden/t16_hidden.clas.abap new file mode 100644 index 0000000..412ce0f --- /dev/null +++ b/tasks_gen/eval/G0025/hidden/t16_hidden.clas.abap @@ -0,0 +1,88 @@ +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 source_a_rows FOR TESTING. + METHODS source_b_rows FOR TESTING. + METHODS all_partners FOR TESTING. + METHODS same_id_twice FOR TESTING. + METHODS name_and_city FOR TESTING. + METHODS only_in_a FOR TESTING. + METHODS only_in_b FOR TESTING. +ENDCLASS. + + +CLASS {{p}}t16_hidden IMPLEMENTATION. + METHOD class_setup. + go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_PARTNER_ALL' ). + ENDMETHOD. + + METHOD class_teardown. + go_env->destroy( ). + ENDMETHOD. + + METHOD setup. + DATA lt_a TYPE STANDARD TABLE OF {{p}}part_a WITH EMPTY KEY. + DATA lt_b TYPE STANDARD TABLE OF {{p}}part_b WITH EMPTY KEY. + go_env->clear_doubles( ). + lt_a = VALUE #( ( partner_id = 'P1' name = 'Alpha GmbH' city = 'Berlin' ) + ( partner_id = 'P2' name = 'Beta AG' city = 'Hamburg' ) ). + lt_b = VALUE #( ( partner_id = 'P2' name = 'Beta AG' city = 'Hamburg' ) + ( partner_id = 'P3' name = 'Gamma KG' city = 'Munich' ) ). + go_env->insert_test_data( lt_a ). + go_env->insert_test_data( lt_b ). + ENDMETHOD. + + METHOD source_a_rows. + SELECT partner_id, source FROM {{p}}i_partner_all + WHERE source = 'A' ORDER BY partner_id INTO TABLE @DATA(lt_rows). + cl_abap_unit_assert=>assert_equals( exp = 2 act = lines( lt_rows ) ). + cl_abap_unit_assert=>assert_equals( exp = 'P1' act = lt_rows[ 1 ]-partner_id ). + cl_abap_unit_assert=>assert_equals( exp = 'P2' act = lt_rows[ 2 ]-partner_id ). + ENDMETHOD. + + METHOD source_b_rows. + SELECT partner_id, source FROM {{p}}i_partner_all + WHERE source = 'B' ORDER BY partner_id INTO TABLE @DATA(lt_rows). + cl_abap_unit_assert=>assert_equals( exp = 2 act = lines( lt_rows ) ). + cl_abap_unit_assert=>assert_equals( exp = 'P2' act = lt_rows[ 1 ]-partner_id ). + cl_abap_unit_assert=>assert_equals( exp = 'P3' act = lt_rows[ 2 ]-partner_id ). + ENDMETHOD. + + METHOD all_partners. + SELECT COUNT(*) FROM {{p}}i_partner_all INTO @DATA(lv_count). + cl_abap_unit_assert=>assert_equals( exp = 4 act = lv_count ). + ENDMETHOD. + + METHOD same_id_twice. + SELECT source FROM {{p}}i_partner_all + WHERE partner_id = 'P2' ORDER BY source INTO TABLE @DATA(lt_rows). + cl_abap_unit_assert=>assert_equals( exp = 2 act = lines( lt_rows ) ). + cl_abap_unit_assert=>assert_equals( exp = 'A' act = lt_rows[ 1 ]-source ). + cl_abap_unit_assert=>assert_equals( exp = 'B' act = lt_rows[ 2 ]-source ). + ENDMETHOD. + + METHOD name_and_city. + SELECT SINGLE name, city FROM {{p}}i_partner_all + WHERE partner_id = 'P3' AND source = 'B' INTO @DATA(ls_row). + cl_abap_unit_assert=>assert_equals( exp = 'Gamma KG' act = ls_row-name ). + cl_abap_unit_assert=>assert_equals( exp = 'Munich' act = ls_row-city ). + ENDMETHOD. + + METHOD only_in_a. + SELECT source FROM {{p}}i_partner_all + WHERE partner_id = 'P1' INTO TABLE @DATA(lt_rows). + cl_abap_unit_assert=>assert_equals( exp = 1 act = lines( lt_rows ) ). + cl_abap_unit_assert=>assert_equals( exp = 'A' act = lt_rows[ 1 ]-source ). + ENDMETHOD. + + METHOD only_in_b. + SELECT source FROM {{p}}i_partner_all + WHERE partner_id = 'P3' INTO TABLE @DATA(lt_rows). + cl_abap_unit_assert=>assert_equals( exp = 1 act = lines( lt_rows ) ). + cl_abap_unit_assert=>assert_equals( exp = 'B' act = lt_rows[ 1 ]-source ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0025/reference/i_partner_all.ddls.asddls b/tasks_gen/eval/G0025/reference/i_partner_all.ddls.asddls new file mode 100644 index 0000000..7ca9847 --- /dev/null +++ b/tasks_gen/eval/G0025/reference/i_partner_all.ddls.asddls @@ -0,0 +1,19 @@ +@AccessControl.authorizationCheck: #NOT_REQUIRED +@EndUserText.label: 'All partners' +@Metadata.ignorePropagatedAnnotations: true +define view entity {{P}}I_PARTNER_ALL + as select from {{p}}part_a as a +{ + key a.partner_id as partner_id, + a.name as name, + a.city as city, + cast('A' as abap.char(1)) as source +} +union all +select from {{p}}part_b as b +{ + key b.partner_id as partner_id, + b.name as name, + b.city as city, + cast('B' as abap.char(1)) as source +} diff --git a/tasks_gen/eval/G0025/reference/t16_test.clas.abap b/tasks_gen/eval/G0025/reference/t16_test.clas.abap new file mode 100644 index 0000000..9758a71 --- /dev/null +++ b/tasks_gen/eval/G0025/reference/t16_test.clas.abap @@ -0,0 +1,45 @@ +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 same_partner_twice FOR TESTING. +ENDCLASS. + + +CLASS {{p}}t16_test IMPLEMENTATION. + METHOD class_setup. + go_env = cl_cds_test_environment=>create( i_for_entity = '{{P}}I_PARTNER_ALL' ). + ENDMETHOD. + + METHOD class_teardown. + go_env->destroy( ). + ENDMETHOD. + + METHOD setup. + DATA lt_a TYPE STANDARD TABLE OF {{p}}part_a WITH EMPTY KEY. + DATA lt_b TYPE STANDARD TABLE OF {{p}}part_b WITH EMPTY KEY. + go_env->clear_doubles( ). + lt_a = VALUE #( ( partner_id = 'A1' name = 'One' city = 'Bonn' ) ). + lt_b = VALUE #( ( partner_id = 'A1' name = 'One' city = 'Bonn' ) + ( partner_id = 'B1' name = 'Two' city = 'Kiel' ) ). + go_env->insert_test_data( lt_a ). + go_env->insert_test_data( lt_b ). + ENDMETHOD. + + METHOD both_sources. + SELECT source FROM {{p}}i_partner_all ORDER BY source INTO TABLE @DATA(lt_rows). + cl_abap_unit_assert=>assert_equals( exp = 3 act = lines( lt_rows ) ). + ENDMETHOD. + + METHOD same_partner_twice. + SELECT source FROM {{p}}i_partner_all + WHERE partner_id = 'A1' ORDER BY source INTO TABLE @DATA(lt_rows). + cl_abap_unit_assert=>assert_equals( exp = 2 act = lines( lt_rows ) ). + cl_abap_unit_assert=>assert_equals( exp = 'A' act = lt_rows[ 1 ]-source ). + cl_abap_unit_assert=>assert_equals( exp = 'B' act = lt_rows[ 2 ]-source ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0025/seed/part_a.tabl.asabap b/tasks_gen/eval/G0025/seed/part_a.tabl.asabap new file mode 100644 index 0000000..92ee9f7 --- /dev/null +++ b/tasks_gen/eval/G0025/seed/part_a.tabl.asabap @@ -0,0 +1,11 @@ +@EndUserText.label : 'Partner list A' +@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE +@AbapCatalog.tableCategory : #TRANSPARENT +@AbapCatalog.deliveryClass : #A +@AbapCatalog.dataMaintenance : #RESTRICTED +define table {{p}}part_a { + key client : abap.clnt not null; + key partner_id : abap.char(10) not null; + name : abap.char(40); + city : abap.char(40); +} diff --git a/tasks_gen/eval/G0025/seed/part_b.tabl.asabap b/tasks_gen/eval/G0025/seed/part_b.tabl.asabap new file mode 100644 index 0000000..778ae2b --- /dev/null +++ b/tasks_gen/eval/G0025/seed/part_b.tabl.asabap @@ -0,0 +1,11 @@ +@EndUserText.label : 'Partner list B' +@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE +@AbapCatalog.tableCategory : #TRANSPARENT +@AbapCatalog.deliveryClass : #A +@AbapCatalog.dataMaintenance : #RESTRICTED +define table {{p}}part_b { + key client : abap.clnt not null; + key partner_id : abap.char(10) not null; + name : abap.char(40); + city : abap.char(40); +} diff --git a/tasks_gen/eval/G0025/seed/seed_partner.clas.abap b/tasks_gen/eval/G0025/seed/seed_partner.clas.abap new file mode 100644 index 0000000..e96af22 --- /dev/null +++ b/tasks_gen/eval/G0025/seed/seed_partner.clas.abap @@ -0,0 +1,24 @@ +CLASS {{p}}seed_partner DEFINITION PUBLIC FINAL CREATE PUBLIC. + PUBLIC SECTION. + INTERFACES if_oo_adt_classrun. + PROTECTED SECTION. + PRIVATE SECTION. +ENDCLASS. + + +CLASS {{p}}seed_partner IMPLEMENTATION. + METHOD if_oo_adt_classrun~main. + DATA lt_a TYPE STANDARD TABLE OF {{p}}part_a WITH EMPTY KEY. + DATA lt_b TYPE STANDARD TABLE OF {{p}}part_b WITH EMPTY KEY. + + lt_a = VALUE #( ( client = sy-mandt partner_id = 'P1' name = 'Alpha GmbH' city = 'Berlin' ) + ( client = sy-mandt partner_id = 'P2' name = 'Beta AG' city = 'Hamburg' ) ). + lt_b = VALUE #( ( client = sy-mandt partner_id = 'P2' name = 'Beta AG' city = 'Hamburg' ) + ( client = sy-mandt partner_id = 'P3' name = 'Gamma KG' city = 'Munich' ) ). + + MODIFY {{p}}part_a FROM TABLE @lt_a. + MODIFY {{p}}part_b FROM TABLE @lt_b. + + out->write( 'Partner seed data written.' ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0025/spec.md b/tasks_gen/eval/G0025/spec.md new file mode 100644 index 0000000..d01b0fb --- /dev/null +++ b/tasks_gen/eval/G0025/spec.md @@ -0,0 +1,37 @@ +# 1. Goal +Two tables hold partner lists. A report and an OData service must read one +single list of all partners. Each row must show from which table it comes. + +# 2. Open questions +None. + +# 3. Context +- Table {{P}}PART_A (partner list A), package $TMP. Key: PARTNER_ID (CHAR 10). + Fields: NAME (CHAR 40), CITY (CHAR 40). +- Table {{P}}PART_B (partner list B), package $TMP. It has the same structure. +- A partner ID can be in both tables. + +# 4. Contract +- Create the CDS view entity {{P}}I_PARTNER_ALL in package $TMP. +- Elements, with these names: PARTNER_ID (key), NAME, CITY, SOURCE. +- SOURCE is one character. +- No authorization check (#NOT_REQUIRED). + +# 5. Business rules +1. The view shows every partner of {{P}}PART_A and every partner of + {{P}}PART_B. No partner is missing. +2. SOURCE is 'A' for a row from {{P}}PART_A. SOURCE is 'B' for a row from + {{P}}PART_B. +3. NAME and CITY are the values of the table that the row comes from. +4. If the same PARTNER_ID is in both tables, the view shows two rows for this + partner. One row has SOURCE 'A'. The other row has SOURCE 'B'. +5. A partner that is only in one table is shown one time. + +# 6. Constraints +- Release target: 8.16. +- Out of scope: do not change {{P}}PART_A, {{P}}PART_B and {{P}}SEED_PARTNER. + +# 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/G0025/task.json b/tasks_gen/eval/G0025/task.json new file mode 100644 index 0000000..5e0bd78 --- /dev/null +++ b/tasks_gen/eval/G0025/task.json @@ -0,0 +1,73 @@ +{ + "id": "G0025", + "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}}PART_A", + "file": "seed/part_a.tabl.asabap", + "description": "Partner list A" + }, + { + "type": "TABL", + "name": "{{P}}PART_B", + "file": "seed/part_b.tabl.asabap", + "description": "Partner list B" + }, + { + "type": "CLAS", + "name": "{{P}}SEED_PARTNER", + "file": "seed/seed_partner.clas.abap", + "description": "Fills both partner tables", + "run": true + } + ], + "contract": [ + { + "type": "DDLS", + "name": "{{P}}I_PARTNER_ALL", + "fields": [ + "PARTNER_ID", + "NAME", + "CITY", + "SOURCE" + ] + } + ], + "out_of_scope": [ + "{{P}}PART_A", + "{{P}}PART_B", + "{{P}}SEED_PARTNER" + ], + "hidden_tests": [ + { + "type": "CLAS", + "name": "{{P}}T16_HIDDEN", + "file": "hidden/t16_hidden.clas.abap", + "description": "T16 hidden tests" + } + ], + "reference": [ + { + "type": "DDLS", + "name": "{{P}}I_PARTNER_ALL", + "file": "reference/i_partner_all.ddls.asddls", + "description": "All partners of both lists with a source element" + }, + { + "type": "CLAS", + "name": "{{P}}T16_TEST", + "file": "reference/t16_test.clas.abap", + "description": "T16 own tests" + } + ], + "craft_checks": [] +} \ No newline at end of file diff --git a/tasks_gen/eval/G0026/generation.json b/tasks_gen/eval/G0026/generation.json new file mode 100644 index 0000000..0a283fd --- /dev/null +++ b/tasks_gen/eval/G0026/generation.json @@ -0,0 +1,37 @@ +{ + "id": "G0026", + "pool": "eval", + "object_type": "FUNC", + "category": "A", + "attempts": [ + { + "stage": "bundle", + "errors": [ + "reference/t14_test.clas.abap line 81: syntax error for release v758 (abaplint): Statement does not exist in the configured ABAP version(or a parser error), \"cl_abap_unit_assert\"" + ], + "by_check": { + "abaplint": 1 + } + }, + { + "stage": "validate", + "oracle": 0, + "null": 0 + }, + { + "stage": "bundle", + "errors": [ + "reference/t14_test.clas.abap line 81: syntax error for release v758 (abaplint): Statement does not exist in the configured ABAP version(or a parser error), \"cl_abap_unit_assert\"" + ], + "by_check": { + "abaplint": 1 + } + }, + { + "stage": "validate", + "oracle": 100.0, + "null": 0 + } + ], + "accepted": true +} \ No newline at end of file diff --git a/tasks_gen/eval/G0026/hidden/t14_hidden.clas.abap b/tasks_gen/eval/G0026/hidden/t14_hidden.clas.abap new file mode 100644 index 0000000..c89c2e2 --- /dev/null +++ b/tasks_gen/eval/G0026/hidden/t14_hidden.clas.abap @@ -0,0 +1,149 @@ +CLASS {{p}}t14_hidden DEFINITION PUBLIC FINAL CREATE PUBLIC + FOR TESTING DURATION SHORT RISK LEVEL HARMLESS. + PRIVATE SECTION. + TYPES: BEGIN OF ty_result, + time_fee TYPE decfloat34, + distance_fee TYPE decfloat34, + surcharge TYPE decfloat34, + discount TYPE decfloat34, + total TYPE decfloat34, + END OF ty_result. + + METHODS price + IMPORTING iv_class TYPE string + iv_minutes TYPE i + iv_km TYPE i + iv_member TYPE abap_bool DEFAULT abap_false + iv_weekend TYPE abap_bool DEFAULT abap_false + EXPORTING es_result TYPE ty_result + ev_subrc TYPE sysubrc. + + METHODS time_fee_blocks FOR TESTING. + METHODS distance_fee_free_km FOR TESTING. + METHODS weekend_surcharge FOR TESTING. + METHODS weekday_no_surcharge FOR TESTING. + METHODS member_discount FOR TESTING. + METHODS minimum_total FOR TESTING. + METHODS minimum_total_member FOR TESTING. + METHODS invalid_duration FOR TESTING. + METHODS invalid_distance FOR TESTING. + METHODS invalid_class FOR TESTING. +ENDCLASS. + + +CLASS {{p}}t14_hidden IMPLEMENTATION. + METHOD price. + CLEAR es_result. + CALL FUNCTION '{{P}}RENT_PRICE' + EXPORTING + iv_vehicle_class = iv_class + iv_minutes = iv_minutes + iv_km = iv_km + iv_member = iv_member + iv_weekend = iv_weekend + IMPORTING + ev_time_fee = es_result-time_fee + ev_distance_fee = es_result-distance_fee + ev_surcharge = es_result-surcharge + ev_discount = es_result-discount + ev_total = es_result-total + EXCEPTIONS + invalid_duration = 1 + invalid_distance = 2 + invalid_class = 3 + OTHERS = 4. + ev_subrc = sy-subrc. + ENDMETHOD. + + METHOD time_fee_blocks. + price( EXPORTING iv_class = `S` iv_minutes = 15 iv_km = 0 + IMPORTING es_result = DATA(ls_15) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 3 ) act = ls_15-time_fee ). + + price( EXPORTING iv_class = `S` iv_minutes = 16 iv_km = 0 + IMPORTING es_result = DATA(ls_16) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 6 ) act = ls_16-time_fee ). + + price( EXPORTING iv_class = `L` iv_minutes = 61 iv_km = 0 + IMPORTING es_result = DATA(ls_61) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 40 ) act = ls_61-time_fee ). + ENDMETHOD. + + METHOD distance_fee_free_km. + price( EXPORTING iv_class = `L` iv_minutes = 60 iv_km = 10 + IMPORTING es_result = DATA(ls_free) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 0 ) act = ls_free-distance_fee ). + + price( EXPORTING iv_class = `L` iv_minutes = 60 iv_km = 14 + IMPORTING es_result = DATA(ls_paid) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 1 ) act = ls_paid-distance_fee ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 33 ) act = ls_paid-total ). + ENDMETHOD. + + METHOD weekend_surcharge. + price( EXPORTING iv_class = `M` iv_minutes = 30 iv_km = 0 iv_weekend = abap_true + IMPORTING es_result = DATA(ls) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '2.50' ) act = ls-surcharge ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '12.50' ) act = ls-total ). + ENDMETHOD. + + METHOD weekday_no_surcharge. + price( EXPORTING iv_class = `M` iv_minutes = 30 iv_km = 0 + IMPORTING es_result = DATA(ls) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 0 ) act = ls-surcharge ). + ENDMETHOD. + + METHOD member_discount. + " 6.50 * 15 % = 0.975 -> 0.98 + price( EXPORTING iv_class = `M` iv_minutes = 15 iv_km = 11 + iv_weekend = abap_true iv_member = abap_true + IMPORTING es_result = DATA(ls_half) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '0.98' ) act = ls_half-discount ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '5.52' ) act = ls_half-total ). + + " 10.75 * 15 % = 1.6125 -> 1.61 + price( EXPORTING iv_class = `M` iv_minutes = 30 iv_km = 13 iv_member = abap_true + IMPORTING es_result = DATA(ls_round) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '1.61' ) act = ls_round-discount ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '9.14' ) act = ls_round-total ). + + " 7.50 * 15 % = 1.125 -> 1.13 + price( EXPORTING iv_class = `S` iv_minutes = 15 iv_km = 28 iv_member = abap_true + IMPORTING es_result = DATA(ls_up) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '1.13' ) act = ls_up-discount ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '6.37' ) act = ls_up-total ). + ENDMETHOD. + + METHOD minimum_total. + price( EXPORTING iv_class = `S` iv_minutes = 15 iv_km = 0 + IMPORTING es_result = DATA(ls) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 3 ) act = ls-time_fee ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 0 ) act = ls-discount ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 5 ) act = ls-total ). + ENDMETHOD. + + METHOD minimum_total_member. + price( EXPORTING iv_class = `S` iv_minutes = 15 iv_km = 0 iv_member = abap_true + IMPORTING es_result = DATA(ls) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '0.45' ) act = ls-discount ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 5 ) act = ls-total ). + ENDMETHOD. + + METHOD invalid_duration. + price( EXPORTING iv_class = `S` iv_minutes = 0 iv_km = 0 + IMPORTING ev_subrc = DATA(lv_subrc) ). + cl_abap_unit_assert=>assert_equals( exp = 1 act = lv_subrc ). + ENDMETHOD. + + METHOD invalid_distance. + price( EXPORTING iv_class = `S` iv_minutes = 15 iv_km = -1 + IMPORTING ev_subrc = DATA(lv_subrc) ). + cl_abap_unit_assert=>assert_equals( exp = 2 act = lv_subrc ). + ENDMETHOD. + + METHOD invalid_class. + price( EXPORTING iv_class = `X` iv_minutes = 15 iv_km = 0 + IMPORTING ev_subrc = DATA(lv_subrc) ). + cl_abap_unit_assert=>assert_equals( exp = 3 act = lv_subrc ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0026/reference/cl_rent.clas.abap b/tasks_gen/eval/G0026/reference/cl_rent.clas.abap new file mode 100644 index 0000000..3e53232 --- /dev/null +++ b/tasks_gen/eval/G0026/reference/cl_rent.clas.abap @@ -0,0 +1,158 @@ +CLASS {{p}}cl_rent DEFINITION + PUBLIC + FINAL + CREATE PUBLIC. + + PUBLIC SECTION. + TYPES ty_class TYPE string. + TYPES ty_money TYPE decfloat34. + TYPES ty_err_code TYPE c LENGTH 1. + + CONSTANTS: BEGIN OF c_err, + ok TYPE ty_err_code VALUE ' ', + duration TYPE ty_err_code VALUE 'D', + distance TYPE ty_err_code VALUE 'K', + bad_class TYPE ty_err_code VALUE 'C', + END OF c_err. + + TYPES: BEGIN OF ty_request, + vehicle_class TYPE ty_class, + minutes TYPE i, + km TYPE i, + member TYPE abap_bool, + weekend TYPE abap_bool, + END OF ty_request. + + TYPES: BEGIN OF ty_result, + err_code TYPE ty_err_code, + time_fee TYPE ty_money, + distance_fee TYPE ty_money, + surcharge TYPE ty_money, + discount TYPE ty_money, + total TYPE ty_money, + END OF ty_result. + + CLASS-METHODS calculate + IMPORTING is_request TYPE ty_request + RETURNING VALUE(rs_result) TYPE ty_result. + + PRIVATE SECTION. + CONSTANTS: BEGIN OF c_block_price, + small TYPE ty_money VALUE '3.00', + medium TYPE ty_money VALUE '5.00', + large TYPE ty_money VALUE '8.00', + END OF c_block_price. + CONSTANTS c_block_minutes TYPE i VALUE 15. + CONSTANTS c_free_km TYPE i VALUE 10. + CONSTANTS c_km_price TYPE ty_money VALUE '0.25'. + CONSTANTS c_weekend_share TYPE ty_money VALUE '0.25'. + CONSTANTS c_member_share TYPE ty_money VALUE '0.15'. + CONSTANTS c_min_total TYPE ty_money VALUE '5.00'. + CONSTANTS c_decimals TYPE i VALUE 2. + + CLASS-METHODS validate + IMPORTING is_request TYPE ty_request + RETURNING VALUE(rv_err_code) TYPE ty_err_code. + + CLASS-METHODS time_fee + IMPORTING is_request TYPE ty_request + RETURNING VALUE(rv_fee) TYPE ty_money. + + CLASS-METHODS distance_fee + IMPORTING is_request TYPE ty_request + RETURNING VALUE(rv_fee) TYPE ty_money. + + CLASS-METHODS block_price + IMPORTING is_request TYPE ty_request + RETURNING VALUE(rv_price) TYPE ty_money. + + CLASS-METHODS block_count + IMPORTING iv_minutes TYPE i + RETURNING VALUE(rv_blocks) TYPE i. + + CLASS-METHODS round_money + IMPORTING iv_value TYPE ty_money + RETURNING VALUE(rv_value) TYPE ty_money. +ENDCLASS. + + +CLASS {{p}}cl_rent IMPLEMENTATION. + METHOD calculate. + rs_result-err_code = validate( is_request ). + IF rs_result-err_code <> c_err-ok. + RETURN. + ENDIF. + + rs_result-time_fee = time_fee( is_request ). + rs_result-distance_fee = distance_fee( is_request ). + + IF is_request-weekend = abap_true. + rs_result-surcharge = round_money( rs_result-time_fee * c_weekend_share ). + ELSE. + rs_result-surcharge = 0. + ENDIF. + + DATA(lv_gross) = rs_result-time_fee + rs_result-distance_fee + rs_result-surcharge. + + IF is_request-member = abap_true. + rs_result-discount = round_money( lv_gross * c_member_share ). + ELSE. + rs_result-discount = 0. + ENDIF. + + rs_result-total = round_money( lv_gross - rs_result-discount ). + IF rs_result-total < c_min_total. + rs_result-total = c_min_total. + ENDIF. + ENDMETHOD. + + METHOD validate. + IF is_request-minutes < 1. + rv_err_code = c_err-duration. + ELSEIF is_request-km < 0. + rv_err_code = c_err-distance. + ELSEIF is_request-vehicle_class <> 'S' + AND is_request-vehicle_class <> 'M' + AND is_request-vehicle_class <> 'L'. + rv_err_code = c_err-bad_class. + ELSE. + rv_err_code = c_err-ok. + ENDIF. + ENDMETHOD. + + METHOD time_fee. + rv_fee = block_count( is_request-minutes ) * block_price( is_request ). + ENDMETHOD. + + METHOD distance_fee. + DATA(lv_chargeable_km) = is_request-km - c_free_km. + IF lv_chargeable_km > 0. + rv_fee = lv_chargeable_km * c_km_price. + ELSE. + rv_fee = 0. + ENDIF. + ENDMETHOD. + + METHOD block_price. + CASE is_request-vehicle_class. + WHEN 'S'. + rv_price = c_block_price-small. + WHEN 'M'. + rv_price = c_block_price-medium. + WHEN 'L'. + rv_price = c_block_price-large. + WHEN OTHERS. + rv_price = 0. + ENDCASE. + ENDMETHOD. + + METHOD block_count. + rv_blocks = ( iv_minutes + c_block_minutes - 1 ) DIV c_block_minutes. + ENDMETHOD. + + METHOD round_money. + rv_value = round( val = iv_value + dec = c_decimals + mode = cl_abap_math=>round_half_up ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0026/reference/cl_rent.clas.testclasses.abap b/tasks_gen/eval/G0026/reference/cl_rent.clas.testclasses.abap new file mode 100644 index 0000000..97246f6 --- /dev/null +++ b/tasks_gen/eval/G0026/reference/cl_rent.clas.testclasses.abap @@ -0,0 +1,87 @@ +CLASS {{p}}lcl_rent_test DEFINITION FOR TESTING + DURATION SHORT RISK LEVEL HARMLESS FINAL. + + PRIVATE SECTION. + METHODS price_for + IMPORTING iv_class TYPE string + iv_minutes TYPE i + iv_km TYPE i + iv_member TYPE abap_bool DEFAULT abap_false + iv_weekend TYPE abap_bool DEFAULT abap_false + RETURNING VALUE(rs_result) TYPE {{p}}cl_rent=>ty_result. + + METHODS time_fee_blocks FOR TESTING. + METHODS distance_fee_free_km FOR TESTING. + METHODS weekend_surcharge FOR TESTING. + METHODS member_discount FOR TESTING. + METHODS minimum_total FOR TESTING. + METHODS invalid_input FOR TESTING. +ENDCLASS. + + +CLASS {{p}}lcl_rent_test IMPLEMENTATION. + METHOD price_for. + DATA(ls_request) = VALUE {{p}}cl_rent=>ty_request( + vehicle_class = iv_class + minutes = iv_minutes + km = iv_km + member = iv_member + weekend = iv_weekend ). + rs_result = {{p}}cl_rent=>calculate( ls_request ). + ENDMETHOD. + + METHOD time_fee_blocks. + DATA(ls_15) = price_for( iv_class = `S` iv_minutes = 15 iv_km = 0 ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 3 ) act = ls_15-time_fee ). + + DATA(ls_16) = price_for( iv_class = `S` iv_minutes = 16 iv_km = 0 ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 6 ) act = ls_16-time_fee ). + + DATA(ls_61) = price_for( iv_class = `L` iv_minutes = 61 iv_km = 0 ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 40 ) act = ls_61-time_fee ). + ENDMETHOD. + + METHOD distance_fee_free_km. + DATA(ls_free) = price_for( iv_class = `L` iv_minutes = 60 iv_km = 10 ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 0 ) act = ls_free-distance_fee ). + + DATA(ls_paid) = price_for( iv_class = `L` iv_minutes = 60 iv_km = 14 ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 1 ) act = ls_paid-distance_fee ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 33 ) act = ls_paid-total ). + ENDMETHOD. + + METHOD weekend_surcharge. + DATA(ls) = price_for( iv_class = `M` iv_minutes = 30 iv_km = 0 iv_weekend = abap_true ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '2.50' ) act = ls-surcharge ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '12.50' ) act = ls-total ). + ENDMETHOD. + + METHOD member_discount. + DATA(ls_half) = price_for( iv_class = `M` iv_minutes = 15 iv_km = 11 + iv_weekend = abap_true iv_member = abap_true ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '0.98' ) act = ls_half-discount ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '5.52' ) act = ls_half-total ). + + DATA(ls_up) = price_for( iv_class = `S` iv_minutes = 15 iv_km = 28 iv_member = abap_true ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '1.13' ) act = ls_up-discount ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '6.37' ) act = ls_up-total ). + ENDMETHOD. + + METHOD minimum_total. + DATA(ls) = price_for( iv_class = `S` iv_minutes = 15 iv_km = 0 ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 3 ) act = ls-time_fee ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 5 ) act = ls-total ). + ENDMETHOD. + + METHOD invalid_input. + cl_abap_unit_assert=>assert_equals( + exp = {{p}}cl_rent=>c_err-duration + act = price_for( iv_class = `S` iv_minutes = 0 iv_km = 0 )-err_code ). + cl_abap_unit_assert=>assert_equals( + exp = {{p}}cl_rent=>c_err-distance + act = price_for( iv_class = `S` iv_minutes = 15 iv_km = -1 )-err_code ). + cl_abap_unit_assert=>assert_equals( + exp = {{p}}cl_rent=>c_err-bad_class + act = price_for( iv_class = `X` iv_minutes = 15 iv_km = 0 )-err_code ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0026/reference/cx_rent_invalid.clas.abap b/tasks_gen/eval/G0026/reference/cx_rent_invalid.clas.abap new file mode 100644 index 0000000..8f08dd6 --- /dev/null +++ b/tasks_gen/eval/G0026/reference/cx_rent_invalid.clas.abap @@ -0,0 +1,30 @@ +CLASS {{p}}cx_rent_invalid DEFINITION + PUBLIC + INHERITING FROM cx_static_check + FINAL + CREATE PUBLIC. + + PUBLIC SECTION. + METHODS constructor + IMPORTING textid LIKE textid OPTIONAL + previous LIKE previous OPTIONAL + iv_reason TYPE string OPTIONAL. + + METHODS get_reason + RETURNING VALUE(rv_reason) TYPE string. + + PRIVATE SECTION. + DATA mv_reason TYPE string. +ENDCLASS. + + +CLASS {{p}}cx_rent_invalid IMPLEMENTATION. + METHOD constructor. + super->constructor( textid = textid previous = previous ). + mv_reason = iv_reason. + ENDMETHOD. + + METHOD get_reason. + rv_reason = mv_reason. + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0026/reference/rent_price.func.abap b/tasks_gen/eval/G0026/reference/rent_price.func.abap new file mode 100644 index 0000000..8a8cd5b --- /dev/null +++ b/tasks_gen/eval/G0026/reference/rent_price.func.abap @@ -0,0 +1,43 @@ +FUNCTION {{p}}rent_price + IMPORTING + iv_vehicle_class TYPE string + iv_minutes TYPE i + iv_km TYPE i + iv_member TYPE abap_bool + iv_weekend TYPE abap_bool + EXPORTING + ev_time_fee TYPE decfloat34 + ev_distance_fee TYPE decfloat34 + ev_surcharge TYPE decfloat34 + ev_discount TYPE decfloat34 + ev_total TYPE decfloat34 + EXCEPTIONS + invalid_duration + invalid_distance + invalid_class. + + DATA ls_request TYPE {{p}}cl_rent=>ty_request. + ls_request-vehicle_class = iv_vehicle_class. + ls_request-minutes = iv_minutes. + ls_request-km = iv_km. + ls_request-member = iv_member. + ls_request-weekend = iv_weekend. + + DATA(ls_result) = {{p}}cl_rent=>calculate( ls_request ). + + CASE ls_result-err_code. + WHEN {{p}}cl_rent=>c_err-duration. + RAISE invalid_duration. + WHEN {{p}}cl_rent=>c_err-distance. + RAISE invalid_distance. + WHEN {{p}}cl_rent=>c_err-bad_class. + RAISE invalid_class. + WHEN OTHERS. + ev_time_fee = ls_result-time_fee. + ev_distance_fee = ls_result-distance_fee. + ev_surcharge = ls_result-surcharge. + ev_discount = ls_result-discount. + ev_total = ls_result-total. + ENDCASE. + +ENDFUNCTION. diff --git a/tasks_gen/eval/G0026/reference/t14_test.clas.abap b/tasks_gen/eval/G0026/reference/t14_test.clas.abap new file mode 100644 index 0000000..3a416d5 --- /dev/null +++ b/tasks_gen/eval/G0026/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. + TYPES: BEGIN OF ty_result, + time_fee TYPE decfloat34, + distance_fee TYPE decfloat34, + surcharge TYPE decfloat34, + discount TYPE decfloat34, + total TYPE decfloat34, + END OF ty_result. + + METHODS price + IMPORTING iv_class TYPE string + iv_minutes TYPE i + iv_km TYPE i + iv_member TYPE abap_bool DEFAULT abap_false + iv_weekend TYPE abap_bool DEFAULT abap_false + EXPORTING es_result TYPE ty_result + ev_subrc TYPE sysubrc. + + METHODS time_fee_blocks FOR TESTING. + METHODS distance_fee_free_km FOR TESTING. + METHODS weekend_surcharge FOR TESTING. + METHODS member_discount FOR TESTING. + METHODS minimum_total FOR TESTING. + METHODS invalid_duration FOR TESTING. +ENDCLASS. + + +CLASS {{p}}t14_test IMPLEMENTATION. + METHOD price. + CLEAR es_result. + CALL FUNCTION '{{P}}RENT_PRICE' + EXPORTING + iv_vehicle_class = iv_class + iv_minutes = iv_minutes + iv_km = iv_km + iv_member = iv_member + iv_weekend = iv_weekend + IMPORTING + ev_time_fee = es_result-time_fee + ev_distance_fee = es_result-distance_fee + ev_surcharge = es_result-surcharge + ev_discount = es_result-discount + ev_total = es_result-total + EXCEPTIONS + invalid_duration = 1 + invalid_distance = 2 + invalid_class = 3 + OTHERS = 4. + ev_subrc = sy-subrc. + ENDMETHOD. + + METHOD time_fee_blocks. + price( EXPORTING iv_class = `S` iv_minutes = 16 iv_km = 0 + IMPORTING es_result = DATA(ls) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 6 ) act = ls-time_fee ). + ENDMETHOD. + + METHOD distance_fee_free_km. + price( EXPORTING iv_class = `L` iv_minutes = 60 iv_km = 14 + IMPORTING es_result = DATA(ls) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 1 ) act = ls-distance_fee ). + ENDMETHOD. + + METHOD weekend_surcharge. + price( EXPORTING iv_class = `M` iv_minutes = 30 iv_km = 0 iv_weekend = abap_true + IMPORTING es_result = DATA(ls) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '2.50' ) act = ls-surcharge ). + ENDMETHOD. + + METHOD member_discount. + price( EXPORTING iv_class = `S` iv_minutes = 15 iv_km = 28 iv_member = abap_true + IMPORTING es_result = DATA(ls) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( '1.13' ) act = ls-discount ). + ENDMETHOD. + + METHOD minimum_total. + price( EXPORTING iv_class = `S` iv_minutes = 15 iv_km = 0 + IMPORTING es_result = DATA(ls) ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 5 ) act = ls-total ). + cl_abap_unit_assert=>assert_equals( exp = CONV decfloat34( 3 ) act = ls-time_fee ). + ENDMETHOD. + + METHOD invalid_duration. + price( EXPORTING iv_class = `S` iv_minutes = 0 iv_km = 0 + IMPORTING ev_subrc = DATA(lv_subrc) ). + cl_abap_unit_assert=>assert_equals( exp = 1 act = lv_subrc ). + ENDMETHOD. +ENDCLASS. diff --git a/tasks_gen/eval/G0026/seed/rent_sample.func.abap b/tasks_gen/eval/G0026/seed/rent_sample.func.abap new file mode 100644 index 0000000..d74cf73 --- /dev/null +++ b/tasks_gen/eval/G0026/seed/rent_sample.func.abap @@ -0,0 +1,25 @@ +FUNCTION {{p}}rent_sample + IMPORTING + iv_vehicle_class TYPE string + iv_minutes TYPE i + iv_km TYPE i + iv_member TYPE abap_bool + iv_weekend TYPE abap_bool + EXPORTING + ev_time_fee TYPE decfloat34 + ev_distance_fee TYPE decfloat34 + ev_surcharge TYPE decfloat34 + ev_discount TYPE decfloat34 + ev_total TYPE decfloat34 + EXCEPTIONS + invalid_duration + invalid_distance + invalid_class. + + CLEAR: ev_time_fee, + ev_distance_fee, + ev_surcharge, + ev_discount, + ev_total. + +ENDFUNCTION. diff --git a/tasks_gen/eval/G0026/spec.md b/tasks_gen/eval/G0026/spec.md new file mode 100644 index 0000000..26cd4d4 --- /dev/null +++ b/tasks_gen/eval/G0026/spec.md @@ -0,0 +1,81 @@ +# 1. Goal +Calculate the price of a finished car-sharing rental. A booking program calls +one function module for each finished rental. The function module must have +the same interface as the sample function module, because the booking program +calls all pricing modules in the same way. + +# 2. Open questions +None. + +# 3. Context +- Function group {{P}}FG_RENT exists in package $TMP. +- Function module {{P}}RENT_SAMPLE exists in this function group. It is the + sample pricing module. Its interface is the interface that all pricing + modules must use. +- All rental data is in the input parameters. No database access is necessary. + +# 4. Contract +Create the function module {{P}}RENT_PRICE in function group {{P}}FG_RENT. +The interface is: + + IMPORTING + iv_vehicle_class TYPE string + iv_minutes TYPE i + iv_km TYPE i + iv_member TYPE abap_bool + iv_weekend TYPE abap_bool + EXPORTING + ev_time_fee TYPE decfloat34 + ev_distance_fee TYPE decfloat34 + ev_surcharge TYPE decfloat34 + ev_discount TYPE decfloat34 + ev_total TYPE decfloat34 + EXCEPTIONS + invalid_duration + invalid_distance + invalid_class + +This interface is the same as the interface of {{P}}RENT_SAMPLE. + +Put the calculation logic in a new class in package $TMP. The function module +must only call this class. The function module must not contain the +calculation itself. The class is your own design decision. The function module +maps the result of the class to its exporting parameters and exceptions. + +# 5. Business rules +1. Check the input in this sequence: + a. If iv_minutes is less than 1, raise INVALID_DURATION. + b. If iv_km is less than 0, raise INVALID_DISTANCE. + c. If iv_vehicle_class is not 'S', not 'M' and not 'L', raise INVALID_CLASS. +2. Charge the rental duration in started 15-minute blocks. A duration of 15 + minutes is one block. A duration of 16 minutes is two blocks. +3. The price of one block depends on the vehicle class: 'S' costs 3.00, 'M' + costs 5.00 and 'L' costs 8.00. The time fee is the number of blocks + multiplied by the block price. +4. The first 10 kilometres of a rental are free. Each further kilometre costs + 0.25. The distance fee is the number of kilometres above 10 multiplied by + 0.25. +5. If iv_weekend is true, the weekend surcharge is 25 % of the time fee. + Otherwise the weekend surcharge is 0. +6. The gross amount is the time fee plus the distance fee plus the weekend + surcharge. +7. If iv_member is true, the member discount is 15 % of the gross amount. + Otherwise the member discount is 0. +8. The total price is the gross amount minus the member discount. If this + value is less than 5.00, the total price is 5.00. +9. Round the weekend surcharge, the member discount and the total price half + up to 2 decimals. +10. Return the time fee in ev_time_fee, the distance fee in ev_distance_fee, + the weekend surcharge in ev_surcharge, the member discount in ev_discount + and the total price in ev_total. + +# 6. Constraints +- Release target: ABAP Platform 2025 (SAP_BASIS 816). +- Coding standards: Clean ABAP. Keep every method below 40 statements. +- Out of scope: do not change {{P}}RENT_SAMPLE. + +# 7. Acceptance +- The function module {{P}}RENT_PRICE is active and has no syntax error. +- The hidden tests pass. +- Write ABAP Unit tests for the new class as local test classes. +- Write ABAP Unit tests for the function module in a global test class. diff --git a/tasks_gen/eval/G0026/task.json b/tasks_gen/eval/G0026/task.json new file mode 100644 index 0000000..93c33fb --- /dev/null +++ b/tasks_gen/eval/G0026/task.json @@ -0,0 +1,111 @@ +{ + "id": "G0026", + "category": "A", + "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_RENT", + "description": "Rental pricing" + }, + { + "type": "FUNC", + "name": "{{P}}RENT_SAMPLE", + "functionGroup": "{{P}}FG_RENT", + "file": "seed/rent_sample.func.abap", + "description": "Sample pricing module" + } + ], + "contract": [ + { + "type": "FUNC", + "name": "{{P}}RENT_PRICE", + "functionGroup": "{{P}}FG_RENT", + "params": [ + { + "name": "iv_vehicle_class", + "type": "string" + }, + { + "name": "iv_minutes", + "type": "i" + }, + { + "name": "iv_km", + "type": "i" + }, + { + "name": "iv_member", + "type": "abap_bool" + }, + { + "name": "iv_weekend", + "type": "abap_bool" + }, + { + "name": "ev_time_fee", + "type": "decfloat34" + }, + { + "name": "ev_distance_fee", + "type": "decfloat34" + }, + { + "name": "ev_surcharge", + "type": "decfloat34" + }, + { + "name": "ev_discount", + "type": "decfloat34" + }, + { + "name": "ev_total", + "type": "decfloat34" + } + ] + } + ], + "out_of_scope": [ + "{{P}}RENT_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_RENT", + "file": "reference/cl_rent.clas.abap", + "testclasses_file": "reference/cl_rent.clas.testclasses.abap", + "description": "Rental price calculation" + }, + { + "type": "FUNC", + "name": "{{P}}RENT_PRICE", + "functionGroup": "{{P}}FG_RENT", + "file": "reference/rent_price.func.abap", + "description": "Rental pricing 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