Rolle im AblaufEnthält das unveränderte Legacy-Verhalten, das durch Charakterisierungstests abgesichert wird.
Im Lesepfad folgt LegacyTestFixture: Erzeugt deterministische Abhängigkeiten und Testdaten für reproduzierbare Beobachtungen.
package com.example.legacy.run7a;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.DayOfWeek;
import java.time.ZoneOffset;
import java.util.*;
// Pattern: Legacy Facade (absichtlich) - diese Klasse zeigt eine Monster-Methode als Lernobjekt.
// Sie ist NICHT das Zielbild. Run 7A nutzt sie, um Verhalten abzusichern, bevor Refactoring beginnt.
public final class LegacyOrderProcessor {
private final LegacyIntegrationGateway gateway;
public LegacyOrderProcessor(LegacyIntegrationGateway gateway) {
this.gateway = Objects.requireNonNull(gateway);
}
// Anti-Pattern: Long Method + Mixed Responsibilities + Hidden Transaction Script.
// Zweck in diesem Run: vorhandenes Verhalten sichtbar und testbar machen.
public LegacyInvoiceResult process(LegacyOrderRequest request) {
List<String> localEvents = new ArrayList<>();
List<String> localAudit = new ArrayList<>();
String orderId = request == null ? "<null>" : request.orderId();
try {
localAudit.add("start process order=" + orderId);
if (request == null) {
localAudit.add("request is null");
return LegacyInvoiceResult.failed("<null>", "INVALID_REQUEST", "request must not be null", localEvents, localAudit);
}
if (blank(request.orderId())) {
localAudit.add("orderId missing");
return LegacyInvoiceResult.failed("<missing>", "INVALID_ORDER_ID", "orderId is required", localEvents, localAudit);
}
if (blank(request.customerId())) {
localAudit.add("customerId missing");
return LegacyInvoiceResult.failed(request.orderId(), "INVALID_CUSTOMER", "customerId is required", localEvents, localAudit);
}
if (request.lines() == null || request.lines().isEmpty()) {
localAudit.add("no lines");
return LegacyInvoiceResult.failed(request.orderId(), "EMPTY_ORDER", "at least one line is required", localEvents, localAudit);
}
if (request.riskyAccount()) {
localAudit.add("risky account blocked customer=" + request.customerId());
localEvents.add("OrderRejected:risky-account");
gateway.audit("risky account rejected " + request.customerId());
return LegacyInvoiceResult.failed(request.orderId(), "CUSTOMER_RISK_BLOCK", "customer is blocked by risk checks", localEvents, merge(localAudit, gateway));
}
BigDecimal subtotal = BigDecimal.ZERO;
BigDecimal digitalSubtotal = BigDecimal.ZERO;
BigDecimal physicalSubtotal = BigDecimal.ZERO;
int physicalQuantity = 0;
int totalQuantity = 0;
Set<String> skusSeen = new LinkedHashSet<>();
boolean containsTaxReduced = false;
boolean containsHazardous = false;
boolean containsGiftEligible = false;
for (int i = 0; i < request.lines().size(); i++) {
OrderLineRequest line = request.lines().get(i);
if (line == null) {
return LegacyInvoiceResult.failed(request.orderId(), "INVALID_LINE", "line " + i + " is null", localEvents, localAudit);
}
if (blank(line.sku())) {
return LegacyInvoiceResult.failed(request.orderId(), "INVALID_SKU", "line " + i + " has no sku", localEvents, localAudit);
}
if (line.quantity() <= 0) {
return LegacyInvoiceResult.failed(request.orderId(), "INVALID_QUANTITY", "quantity must be positive for " + line.sku(), localEvents, localAudit);
}
if (line.unitPrice() == null || line.unitPrice().signum() < 0) {
return LegacyInvoiceResult.failed(request.orderId(), "INVALID_PRICE", "price invalid for " + line.sku(), localEvents, localAudit);
}
if (!skusSeen.add(line.sku())) {
localAudit.add("duplicate sku merged logically=" + line.sku());
}
BigDecimal lineTotal = line.unitPrice().multiply(BigDecimal.valueOf(line.quantity())).setScale(2, RoundingMode.HALF_UP);
subtotal = subtotal.add(lineTotal);
totalQuantity += line.quantity();
if (line.digital()) {
digitalSubtotal = digitalSubtotal.add(lineTotal);
} else {
physicalSubtotal = physicalSubtotal.add(lineTotal);
physicalQuantity += line.quantity();
int available = gateway.availableStock(line.sku());
localAudit.add("stock check sku=" + line.sku() + " requested=" + line.quantity() + " available=" + available);
if (available < line.quantity()) {
localEvents.add("OrderRejected:stock-unavailable:" + line.sku());
return LegacyInvoiceResult.failed(request.orderId(), "STOCK_UNAVAILABLE", "not enough stock for " + line.sku(), localEvents, merge(localAudit, gateway));
}
}
if ("REDUCED".equalsIgnoreCase(line.taxCategory())) containsTaxReduced = true;
if (line.sku().startsWith("HZ-")) containsHazardous = true;
if (line.giftEligible()) containsGiftEligible = true;
}
if (subtotal.compareTo(new BigDecimal("10000.00")) > 0) {
localEvents.add("OrderRejected:manual-approval-required");
return LegacyInvoiceResult.failed(request.orderId(), "MANUAL_APPROVAL_REQUIRED", "subtotal exceeds automatic limit", localEvents, localAudit);
}
if (containsHazardous && !"DE".equalsIgnoreCase(request.country())) {
localEvents.add("OrderRejected:hazardous-cross-border");
return LegacyInvoiceResult.failed(request.orderId(), "HAZARDOUS_CROSS_BORDER", "hazardous goods only domestic", localEvents, localAudit);
}
BigDecimal discount = BigDecimal.ZERO;
String tier = request.customerTier() == null ? "STANDARD" : request.customerTier().toUpperCase(Locale.ROOT);
if ("PLATINUM".equals(tier)) {
discount = discount.add(subtotal.multiply(new BigDecimal("0.10")));
localAudit.add("tier discount platinum 10%");
} else if ("GOLD".equals(tier)) {
discount = discount.add(subtotal.multiply(new BigDecimal("0.07")));
localAudit.add("tier discount gold 7%");
} else if ("SILVER".equals(tier)) {
discount = discount.add(subtotal.multiply(new BigDecimal("0.03")));
localAudit.add("tier discount silver 3%");
}
if (!blank(request.couponCode())) {
String coupon = request.couponCode().trim().toUpperCase(Locale.ROOT);
if ("WELCOME10".equals(coupon) && subtotal.compareTo(new BigDecimal("50.00")) >= 0) {
discount = discount.add(new BigDecimal("10.00"));
localAudit.add("coupon WELCOME10 applied");
} else if ("FREESHIP".equals(coupon)) {
localAudit.add("coupon FREESHIP deferred to shipping");
} else if ("VIP20".equals(coupon) && ("GOLD".equals(tier) || "PLATINUM".equals(tier))) {
discount = discount.add(subtotal.multiply(new BigDecimal("0.20")));
localAudit.add("coupon VIP20 applied");
} else {
localEvents.add("OrderRejected:invalid-coupon:" + coupon);
return LegacyInvoiceResult.failed(request.orderId(), "INVALID_COUPON", "coupon not applicable: " + coupon, localEvents, localAudit);
}
}
discount = discount.setScale(2, RoundingMode.HALF_UP);
if (discount.compareTo(subtotal.multiply(new BigDecimal("0.40"))) > 0) {
localAudit.add("discount capped at 40%");
discount = subtotal.multiply(new BigDecimal("0.40")).setScale(2, RoundingMode.HALF_UP);
}
BigDecimal shipping = BigDecimal.ZERO;
if (physicalQuantity > 0) {
shipping = new BigDecimal("4.90");
if (physicalSubtotal.compareTo(new BigDecimal("100.00")) > 0) shipping = BigDecimal.ZERO;
if (request.expressShipping()) shipping = shipping.add(new BigDecimal("12.00"));
if ("FREESHIP".equalsIgnoreCase(String.valueOf(request.couponCode()))) shipping = BigDecimal.ZERO;
if (!"DE".equalsIgnoreCase(request.country())) shipping = shipping.add(new BigDecimal("9.00"));
}
if (request.giftWrap()) {
if (!containsGiftEligible) {
return LegacyInvoiceResult.failed(request.orderId(), "GIFT_WRAP_NOT_ALLOWED", "no gift eligible line", localEvents, localAudit);
}
shipping = shipping.add(new BigDecimal("3.50"));
}
shipping = shipping.setScale(2, RoundingMode.HALF_UP);
BigDecimal taxableBase = subtotal.subtract(discount).max(BigDecimal.ZERO);
BigDecimal taxRate = "DE".equalsIgnoreCase(request.country()) ? new BigDecimal("0.19") : new BigDecimal("0.00");
if (containsTaxReduced && "DE".equalsIgnoreCase(request.country())) {
taxRate = new BigDecimal("0.12"); // Legacy bug/behavior: mixed basket gets blended simplified rate.
localAudit.add("legacy blended reduced tax rate 12%");
}
BigDecimal tax = taxableBase.multiply(taxRate).setScale(2, RoundingMode.HALF_UP);
BigDecimal total = taxableBase.add(shipping).add(tax).setScale(2, RoundingMode.HALF_UP);
DayOfWeek dow = gateway.now().atZone(ZoneOffset.UTC).getDayOfWeek();
if (request.expressShipping() && (dow == DayOfWeek.SATURDAY || dow == DayOfWeek.SUNDAY)) {
localAudit.add("express weekend handling fee");
total = total.add(new BigDecimal("5.00")).setScale(2, RoundingMode.HALF_UP);
}
for (OrderLineRequest line : request.lines()) {
if (!line.digital()) {
boolean reserved = gateway.reserveStock(request.orderId(), line.sku(), line.quantity());
if (!reserved) {
localEvents.add("OrderRejected:race-stock-unavailable:" + line.sku());
return LegacyInvoiceResult.failed(request.orderId(), "STOCK_RACE_LOST", "stock changed for " + line.sku(), localEvents, merge(localAudit, gateway));
}
}
}
PaymentDecision payment = gateway.authorizePayment(request.orderId(), request.customerId(), request.paymentMethod(), total);
if (!payment.approved()) {
localEvents.add("OrderRejected:payment:" + payment.reason());
return LegacyInvoiceResult.failed(request.orderId(), "PAYMENT_DECLINED", payment.reason(), localEvents, merge(localAudit, gateway));
}
String invoiceId = gateway.nextInvoiceId();
gateway.saveInvoice(invoiceId, total);
String invoicePayload = request.orderId() + ":" + invoiceId + ":" + total.toPlainString();
gateway.publishEvent("InvoiceCreated", invoicePayload);
gateway.publishEvent("OrderAccepted", request.orderId() + ":" + payment.transactionId());
localEvents.add("InvoiceCreated:" + invoiceId);
localEvents.add("OrderAccepted:" + request.orderId());
localAudit.add("finished success invoice=" + invoiceId + " total=" + total.toPlainString());
return new LegacyInvoiceResult(true, request.orderId(), invoiceId, subtotal.setScale(2, RoundingMode.HALF_UP), discount,
shipping, tax, total, "-", "-", List.copyOf(localEvents), merge(localAudit, gateway));
} catch (RuntimeException ex) {
localAudit.add("technical exception " + ex.getClass().getSimpleName() + ":" + ex.getMessage());
return LegacyInvoiceResult.failed(orderId, "TECHNICAL_FAILURE", ex.getMessage(), localEvents, merge(localAudit, gateway));
}
}
private static boolean blank(String text) { return text == null || text.trim().isEmpty(); }
private static List<String> merge(List<String> local, LegacyIntegrationGateway gateway) {
List<String> merged = new ArrayList<>(local);
if (gateway instanceof DeterministicLegacyGateway d) merged.addAll(d.auditTrail());
return List.copyOf(merged);
}
}