A status that decides what you can do, not just what you see
The usual way to build a lead list is to render every action on every row and
check permissions when one is clicked. It is easier to write, and it produces a
screen where half the buttons return an error.
Here the status is the gate. A lead sitting at New offers Edit, Assign To Me,
Lost, Invalid and Cancel. Once it is Paid the closing actions are gone and
Payments appears. Once it is Closed, Invalid or Canceled there is
nothing but Details. The row does not show you something and then refuse it.
Two conditions combine to decide each action, and it is worth being precise
because the combination is the whole design:
- Status, which says what is legal for a lead in that state.
- Ownership, which says whether you are the person it is legal for.
So Edit is offered on a lead that is still New — nobody owns it yet — or on one
already assigned to you. The same pair governs Lost, Invalid and Cancel. The
effect at a desk is that a rep opening a colleague’s lead sees a read-only
record, not a set of controls that will reject them.
The cost of doing it this way is that the rules live in two places — the front
end decides what to render, the API decides what to accept — and the two have to
agree. That duplication is deliberate. The alternative, deciding only on the
server, produces a screen full of buttons that fail, and reps stop trusting the
interface within a week.
Recording money and confirming money are different jobs
A rep records a payment: amount, currency, which account it landed in, and up to
three attachments, because the evidence for one payment in this market is
routinely a transfer screenshot plus a bank slip plus a receipt.
That payment is created as New. It counts towards nothing. An Admin opens it,
checks the attachments against the account, and either approves or rejects it.
Only Admins see those two buttons, and only while the payment is still New.
This is the oldest control in bookkeeping and it is nearly always the thing a
small CRM leaves out, because when the team is six people it feels like
bureaucracy. It stops feeling like bureaucracy the first time a figure is
disputed. The point is not that anyone is suspected — it is that without the
separation, an honest mistake and a dishonest entry leave exactly the same
trace, so neither can be ruled out.
One deliberate asymmetry: a payment still marked New can be withdrawn by whoever
is looking at it, because a payment entered against the wrong lead should be
easy to take back before it has been relied on. Once approved it is fixed. The
reversible window closes at the moment somebody else has vouched for it.
Attribution has to survive all the way to the money
Ad platforms will tell you what a lead cost. They cannot tell you what a
customer cost, because the payment happens weeks later somewhere they cannot
see.
So every lead carries three separate fields, and separating them is the whole
trick:
- Page — the exact capture point. Facebook Lead Ad — Web Dev is a
different Page from Facebook Lead Ad — Marketing, even though both are
Facebook.
- Source — the channel in the general sense: Facebook Ads, Google Ads,
Website, Referral, LinkedIn, Cold Outreach.
- Service Type — what they actually want, which is what makes demand by
service line visible.
Payments belong to leads, so a payment inherits all three without anyone
re-entering them. Filtering the queue by Page and status is therefore enough to
answer which specific ad produced paying customers rather than merely enquiries
— and Pages are rows in the database, managed in the app, so launching a campaign
and being able to measure it are the same action instead of a feature request.
Targets are dated rows, not a number you overwrite
The obvious way to store a monthly target is a field on the user that you update
when it changes. It works until somebody asks how a rep did in March, after the
number was raised in June.
A target is a row: a user, an amount, and an effective-from date. Raising
somebody’s number writes a new row. The old one stays exactly as it was, which
means every historical month is still measured against the target that was
actually in force at the time, and a rep cannot be retroactively judged against a
figure set after the fact.
Achievement is then computed rather than entered — target against what the
payments for that month actually add up to. The person being measured does not
type in the measurement, which is the only reason the leaderboard is worth
displaying.