Most field lock bugs you can find by stepping through the obvious path: breakpoint at the BAdI method, check the condition, see why it's false. The annoying ones are the BAdIs that execute exactly when they're supposed to, run the locking logic, and still produce the wrong result, because the bug isn't in the logic at all. It's in a single constant somewhere that means something different from what whoever wrote it assumed.
I had one of these on a rechargeable indicator field that needed to lock after goods receipt, so nobody could flip a project cost flag retroactively once material had actually been received against the PO. The requirement was simple. The implementation lived in a field selection exit, ZXM06O01, hanging off the purchasing screen enhancement, and the locking condition checked a transaction context indicator to decide whether the current screen was a goods receipt follow on view or something else entirely.
The bug: the condition checked for the wrong value of that indicator. It was written to lock the field when the indicator equaled one value, when the actual context that mattered, the one that fires when you're looking at a PO after a GR has posted against it, carries a different value. The field selection exit ran every single time. The locking method got called. The condition just evaluated false in exactly the scenario it was supposed to catch, and true in scenarios where the requirement didn't apply.
Why this is worse than a crash
A runtime error gets noticed in the first test cycle. A wrong constant in a boolean condition doesn't announce itself. The field stayed editable in the goods receipt scenario, which is the only scenario that mattered for the requirement, and locked correctly in three or four other unrelated transaction contexts where it happened to also be technically true that the field should be locked, purely by coincidence of overlapping business rules. UAT testers checking whether the field locks somewhere found that yes, it locks, somewhere, and signed off. The actual requirement, lock after GR specifically, silently never fired.
It took going back to the functional spec and re-deriving exactly which transaction context the requirement described, then checking that against the actual constant being compared in the exit, to find the mismatch. One value, swapped for an adjacent one that meant something close but not identical.
The lesson that actually generalizes
When you're implementing any logic gated on a transaction type, movement category, or process indicator field, don't trust your memory of what the value means, and don't trust the previous developer's comment either, because comments drift out of sync with code faster than anyone admits. Pull the domain or the value table directly and confirm the value against the specific scenario you're trying to gate on, not the scenario you assume is adjacent to it. A transaction indicator with five or six possible values, where two of them look similar in a comment but mean genuinely different things in the business process, is exactly the kind of detail that survives code review untouched, because reviewers check that the logic structure makes sense, not that the magic number inside it is the right magic number.
If a BAdI or exit is firing correctly but the outcome is wrong, stop debugging the control flow. Go check what the value actually represents, not what you think it represents. That's usually where it's hiding.