Call him Ray. He is a composite of various people I have met, and none of what follows is unusual.
Ray is in his early sixties. He started in the plant, moved into what was then called data processing, and has written or inherited nearly every piece of custom code the company runs. He has started saying, in the tone you use when you are testing how a sentence sounds, that he might go at the end of next year.
None of it is written down. Not because anyone is negligent. Because there has never been a week where writing it down was more urgent than the thing that broke that morning.
Your ERP vendor maintains their product. Nobody maintains your code. What decides whether that is a live problem for you is not the age of the code — it is how few people can read it.
What breaks, and when
What does not happen is the instructive part. The ERP keeps running: orders enter, invoices print, the general ledger closes. For two weeks the conclusion is that the succession worry was overblown. Then, roughly in this order.
A nightly job fails. It does not crash. It stops and waits for an answer on a system message queue that is nobody’s assigned responsibility, until somebody notices the next morning. Ray used to catch it. He knew that a job stopping on the second step rather than the fourth meant the upstream file had arrived late: reply, resubmit, done before the 6 a.m. production meeting. For two days the plant works from a stale schedule, which reads as a planning problem, so nobody escalates.
A dealer submits an order with a combination the configurator allows and the pricing rules do not cover. Historically that produced a quote wrong by a few hundred dollars, and Ray ran a report of his own each week and worked the exceptions by hand before invoicing. The report still runs. It lands in a mailbox nobody opens, and the exceptions go out the door priced as the system priced them.
The annual price increase arrives from a raw material supplier. Somebody has to apply it. It is not a spreadsheet you paste into a screen — it is a set of multipliers that feed a table that feeds a program that adjusts for dealer tier, freight allowance, program level and a discount waterfall with an order of operations that exists in exactly one place: a block of code nobody has touched in a decade. The price update takes eleven weeks instead of four days.
Then a customer asks for something the business has always been able to do — a new option, a different way of grouping products for a national account — and nobody knows how to make the system do it. That is the real event. Everything before it was something you recover from. This one is a permanent reduction in what the company can do.
None of it appears on an IT report. It appears as margin. The mispriced exceptions ship at whatever the system said until somebody finds them in a variance, usually a quarter later. The configuration you can no longer quote is revenue that goes to whoever can quote it.
The price increase is the one you can actually put a number on, so run that arithmetic. Eleven weeks is a fifth of a year. If the supplier increase should have moved your prices by three percent, and a fifth of the year’s orders went out at the old number, you gave away about six tenths of a percent of annual revenue — all of it margin, none of it recoverable. On forty million dollars of orders that is roughly two hundred and fifty thousand dollars. Substitute your own volume and your own increase. Nothing about the result will show up in an IT budget line.
What the support contract covers
Your ERP vendor maintains their product. That is the whole scope of what you pay for every year, and it is defensible — no software company can warrant code it has never seen. If their order entry program has a defect, they will fix it.
The custom layer is a different matter. The programs, the interfaces, the scheduled jobs, the pricing and configuration rules, the report the plant manager has collected off the printer every morning for years — none of that sits inside any contract the company pays for. The vendor did not write it and cannot see it. The integrator finished the project and moved on. Your managed services provider monitors infrastructure and would tell you, correctly, that application logic is out of scope.
So the coverage map looks like this. The package you did not modify: supported. The hardware and the operating system: supported. Your network: supported. The layer that encodes how your company actually competes: Ray.
All three of those supported lines assume a current maintenance agreement and a release the vendor still supports. A fair number of long-lived manufacturers discover at the worst possible moment that hardware maintenance lapsed on an older box, that they are two releases past the end of application support, or that the vendor will not reproduce an issue on a system with modified objects in it — even when the defect is in code they wrote.
Two answers come up. Neither works. The first is to hire a contractor who can read the language. You can; the language is learnable. What you are buying is hands, and hands are the purchasable part. Nobody can sell you the reason the discount rule has an exception for one account, or what the correction factor in the yield calculation represents and whether it was ever meant to be permanent. The second is to have Ray write it down before he goes. That fails for the reason it has failed for twenty years: documentation never outranks the thing that broke that morning.
How it got this way
No one decided this. It accreted.
A manufacturer buys a package. It handles eighty percent of the business, and the twenty percent that is specific to the company gets built — some of it by consultants, most of it eventually in-house, because in-house is faster and the person doing it understands the business. Then a CAD system needs to talk to the ERP, so there is an interface. Then EDI for two big customers. Then a dealer portal, which needs pricing, so it calls the pricing logic, which by now is a family of programs rather than one. Somebody writes a nightly job to reconcile the portal against the ERP because the two disagree and nobody has time to fix it properly. The temporary reconciliation job runs for fourteen years.
Every one of those decisions was correct at the time. The cost of each was small and immediate; the cost of the whole is large and deferred, and it comes due on a date set by a person’s retirement rather than by anything in a budget cycle.
Nobody catches it because maintenance of custom code is not an event but an absence: it presents as nothing happening, and nothing happening does not get reviewed at a management meeting. The custom layer becomes furniture — in the building, holding weight, nobody having looked underneath it in years.
The platform is rarely the hard part. A long-lived system that does not fall over removes the usual prompts to look inside — a box that never breaks does not generate meetings — and modernization then gets sold as a platform decision when the decision that matters is elsewhere. Moving the code changes what it looks like, not the arithmetic, which is that a finite number of people can read it and they are all roughly the same age.
Three kinds of code
The custom layer gets treated as one undifferentiated mass — a single block of old code to be dealt with entirely or not at all, which is why it never gets dealt with.
When I do a legacy ERP assessment, the first useful output is not a migration plan but a sorted inventory. Everything you wrote alongside the package falls into one of three categories, and they deserve completely different treatment.
Modified vendor code is a separate problem rather than a category. Changes made directly to the vendor’s own source and objects have to be re-applied or re-evaluated at every release. That is the single largest driver of upgrade cost, the usual reason a company is frozen three versions back, and often the reason support gets refused. Inventory it separately and price it separately.
Customization you no longer need. In the inventories I have done this is routinely a quarter to a half of it: programs for a product line discontinued years ago, reports for a customer you lost. It sits in the regression surface of every upgrade and inflates every migration quote you receive. The correct action is to prove it is dead and delete it, and proving it is largely mechanical. The platform has been recording last-used dates the whole time; combined with a cross-reference of what calls what — including SQL and interface callers, not just program calls — that gets you most of the way. Objects whose history was reset by a hardware move, an upgrade or a disaster-recovery test are unknown, not dead; leave them alone. The full fiscal year of evidence is usually something you have to start collecting rather than something you can look up, because job logs get cleaned up and history retention is measured in weeks. Turn the logging on now, and until you have that year, treat anything that plausibly runs annually — year-end, physical inventory, rebate settlement, price-list rollover — as live.
Business logic that is genuinely your advantage. Pricing, configuration, product rules, yield and optimization logic, the way an order becomes a bill of materials and a routing. This is worth real money, and the only category with a hard deadline, because it is the only one you cannot recover from the code alone. The code tells you what the rule is. Only the person tells you why — which of the six conditions in that IF statement is deliberate commercial policy and which is a workaround for a bug in a system decommissioned fifteen years ago. Get that wrong in a migration and you reproduce that workaround as a permanent feature of the new system.
Integration plumbing. The interfaces, the file drops, the scheduled jobs, the reconciliations. This is plumbing between systems, and the most tractable of the three, but only in part. The interface contract — what goes in, what comes out, in what shape, on what schedule — is observable, and that is what makes it tractable. The failure behavior is not. Retry and restart semantics, duplicate suppression, resequencing, partner-specific EDI quirks, the branch that fires twice a year: none of that appears in a normal observation window, and all of it has to be read out of the code. The nightly job in the first section is exactly that case. Rebuild plumbing when you have a reason to, not because it is old.
Sorting this way turns one unfundable question — what do we do about the legacy system — into three ordinary ones with different budgets and owners. Dead code and plumbing can wait. Business logic cannot, and its dates are set by somebody’s retirement plans whether or not anyone writes them on a calendar.
Whether your team can run what comes next
One question sits underneath all three. Suppose you solve the documentation problem completely; you still have to ask whether the people you employ can operate what comes next.
Moving pricing logic off the legacy platform and onto a modern service does not remove the staffing problem. It swaps two long-tenured people for a larger team in a labor market that turns over, on a platform that changes underneath you and needs continuous attention and spend. That is frequently the right trade. It is not a smaller commitment, only a differently shaped one, and the honest version of the business case says so. That answer changes the roadmap more often than the technology does.
The knowledge is still recoverable
The expensive knowledge is recoverable cheaply while Ray is on the payroll, and only while. Sit with him for a day and you get more than a month of forensic analysis would produce. Better, extract the pricing rules from the code, replay them against a year of your own historical quotes, reconcile line by line, and when a case disagrees walk down the hall and ask why.
That replay has a prerequisite worth naming, because it decides whether the result means anything. You need the price and discount tables as they stood on each quote date — from effective-dated tables, archived copies, or a restore of those files from backup. Legacy pricing tables are typically updated in place with no history; the annual increase described above overwrites exactly the data you would be measuring against, and a naive replay produces a wall of differences caused by table changes rather than logic errors. Where that history does not exist, replay against the most recent period the tables can be reconstructed for, and say so in the accuracy figure.
Done properly, that is the difference between a specification with a measured accuracy figure and one somebody hopes is right. In make-to-order quoting the reconciliation is the whole job, because the quote is the definition of the product rather than a document that precedes it. A quoting system that gets a rule subtly wrong does not produce a wrong document. It produces a wrong cut list.
After he leaves the same work still gets done. It costs several times as much, and the result carries an uncertainty you can never close. Teams routinely spend months rediscovering rules the retired author could have walked them through in a week.
The industry packages are real, and that is worth saying plainly. A cabinet manufacturing ERP, or a woodworking or closet configurator, ships genuine machinery: construction rules, door and drawer parametrics, cut lists, nesting and optimization, a dealer quoting front end. Plenty of manufacturers run one rather than building their own. What the packages do not ship is your rules — the exceptions, the tiers, the substitutions, the discount waterfall, the programs you honor for three dealers and no one else. Those get bolted onto whatever you bought, and every make-to-order manufacturer ends up owning its own configure-price-quote layer, because those rules are why you win the order instead of the plant down the road.
Capturing what you have is a separate job from deciding what to do about it, and the two usually get sold together as a platform migration, which is large, expensive and frequently premature. The capture is worth doing on its own, and the answer it produces is often that no migration is needed yet. When one is, the inventory is the input that matters most: the failed migrations I have been asked to review failed on custom logic nobody understood, not on the package.
Put an ERP replacement out to bid without that inventory and the integrator prices discovery as time and materials, because nobody can fix a price on code they have not seen. The inventory is what makes a fixed price possible. You pay for that investigation either way. Only the leverage changes.
Sometimes the answer is to leave it alone
Plenty of manufacturers do not have this problem as badly as they fear.
If your custom layer is small, if two or three people can each read all of it, if they are not all the same age, and if it changes a few times a year without incident, then you have an ordinary maintenance situation rather than a succession cliff. Sit down with the people who know the pricing rules and record why each exception exists — not the logic, which is in the code, but the intent, which is not. A few days of that is cheap insurance. Then spend your attention somewhere it earns more. A stable system with a living bench of people who understand it is not technical debt. It is a paid-off asset, and being told to replace it is usually someone else’s sales process.
The test is not the age of the code, and it is not what a vendor’s roadmap slide says about your release. The test is a subtraction. Take the two or three people who understand the custom layer, remove them one at a time, and ask what stops working, how quickly, and who would notice. If you can answer in detail, you are in fine shape. If the answer is a shrug and a name, the age of the code was never the issue.
Answering in detail means having the inventory: every custom program, interface and scheduled job, mapped to what it does, who understands it, and what breaks when they are unavailable. That is roughly what a readiness assessment produces, and for many companies it is the only engagement they need — the output is a decision, and the decision is frequently to leave most of it alone. Most of the IT advisory work I do in manufacturing starts there. A fair amount of it ends there too, which is the point.