`booked_asset` stores an asset record ID, and `npm run seed` deletes and
re-creates every asset — so the id a line was booked against stops existing after
a seed. The line then points at nothing and silently drops out of monthly_income,
which is exactly the reported symptom: booked yesterday, unbooked today, with the
line still carrying the correct code.
The booking step now re-derives the pointer from the register code the line
already carries (`asset_code`, falling back to the machine code) whenever the
stored target is empty or no longer exists. It only ever points a line at the
asset its own code names, so it never invents a match and never clears a
site booking. A line with no matching code stays unbooked exactly as before.
This makes the documented run order self-healing:
`npm run seed && node scripts/book-invoice.mjs` now restores the links, so a
reset cannot silently unbook the register.
Verified by running a full seed and re-booking: 29 pointers were invalidated by the
seed and all 29 were re-linked, giving 45 posted rows and 3 unbooked lines — the
two MPLANT05 containers and one WT02 trailer, which have no matching plant item.