A survey appointment books a date against an address; a service request attaches to a machine already installed, or to a plan already running. A shared form with a type field labels the request; the distinction has to live in the record that stores what each one attaches to. At Diotek a survey holds an address and a time window but no product code, because which unit fits is what the visit decides.
When every request lands on one form, the queue is sorted by phone
Diotek installs and services air conditioning and ventilation systems. Two unlike requests reach a company like that. One comes before a sale. The other comes after an installation, and it concerns a machine already on a wall. Both used to arrive over the same phone line, and what a caller actually wanted only became clear once someone asked. Classification was a human task, performed live, with no record of the decision.
The cost of that arrangement shows up at the start of a season. Heating or cooling demand concentrates into the same few weeks, and the two request types stack on top of each other. A visitor who reaches a busy line rarely calls back. The site could not catch that visitor either, because it stood outside the flow as a brochure with a phone number on it.
The failure is not slow staff. It is that intake was bound to a single channel, and a synchronous one at that. A phone call requires both parties to be free at the same moment. Every moment that condition fails, a request that existed in someone’s head never becomes a request in the business. In the current setup, intake no longer depends on the phone line; survey and service requests arriving outside working hours still land as written records. Because they are written, who asked for what, and when, no longer depends on anybody remembering the call.
A written record gives operations a second thing as well: an order. Requests taken by phone carry no reliable sequence, because some of them never arrived at all. A written record carries the moment it was created on itself. A team opens the day by looking at a queue. That is the step that has to be settled before the two types are split at all — the split only starts to mean something once every request lands somewhere.
None of this pays off everywhere. If request volume is low and every incoming request is the same shape, a second flow carries nothing. Adding a second queue to a brochure site that needs one contact form just adds an empty box to maintain. What forces the separation is not the number of requests. It is that the requests are not alike.
A site survey cannot behave like a breakdown call
In this trade, unit capacity depends on the space. Room volume, insulation and which way the wall faces decide what equipment will do the job, so nobody can quote before seeing the place. That is why a survey appointment centers on the location. A visitor asks for a date for an address before choosing any equipment. The request carries no product code at all — it carries an address and a window of time.
A service request is built from the opposite end. A machine is already installed, and the request attaches to that machine. Maintenance and fault reports open from the site and reach operations in their own flow, without entering the survey queue. Because the queues are separate, one of them swelling at season peak does not slow the other down.
On Diotek’s HVAC site we built these as two flows from the first day rather than one flow with a type field bolted on afterwards. The two records ask for different things from the start: the survey record for an address and a window, the service record for the unit it concerns.
We did consider the cheaper-looking route: one form, plus a request type field. It falls apart at validation. A survey needs an address and does not need a unit; a service request needs the unit and can manage without a detailed address. On one shared form you cannot make both mandatory, and if you make both optional the record is created incomplete. An incomplete record sends the work straight back to the phone, which is the problem you started with. Pushing the distinction down to a field does not remove it. It relocates it to a layer that cannot resolve it.
Holding the hour is not the same as holding the plan
Dental treatment rarely finishes in one visit. A plan is drawn tooth by tooth: which procedure goes on which tooth, ordered into sessions that spread across weeks and get revised along the way. The plan is therefore its own record, standing independently of any calendar entry.
In the dental clinic CRM, where each appointment attaches to a specific step of that plan, the calendar is not a second copy of the treatment. It is the projection of the plan onto time. When a step changes, or a session is inserted, the calendar moves with it. No appointment is left pointing at nothing, because there is always a step on the other end of the link.
Before that, the clinic ran on a general-purpose booking tool. The tool held the hour and never the step. Where a patient stood in treatment had to be reconstructed from the calendar: scroll back through past appointments, remember which ones were cancelled, count the remaining sessions in your head. Because that reconstruction happened fresh each time, two people looking at the same patient could arrive at two different answers, and neither could prove the other wrong.
What carries the difference here is not a screen. It is a link. The appointment record stores which plan step it belongs to. Since the link lives in the record, a change to the plan reaches the calendar without anyone dragging appointments around.
A second benefit shows up in reporting. Because the plan stands as its own record, how many steps are done, how many are waiting and which session slipped can all be read off the plan rather than counted by scrolling the calendar. A clinic manager sees where a patient stands in treatment on one screen.
For work that finishes in a single session — one consultation, one examination, one measurement — binding an appointment to a plan step adds a layer with nothing in it. If the plan has exactly one step, the appointment already is the plan. What justifies the link is that its steps depend on each other’s order.
A booking, a cancellation and a no-show are three different events
In a Pilates studio, customers do not pay per class. They buy packages. That one fact changes what a booking record means: it stops being a block that occupies space on a calendar and becomes a movement that posts against a balance.
In the Pilatesco system, where a booking, a cancellation and a no-show are kept as three distinct records, writing a booking to the calendar deducts one class credit from the package. The balance sits on the customer record rather than in a separate ledger. Cancellation, no-show and a completed class each post to that balance differently, and which case returns the credit is written as a rule instead of being negotiated case by case.
On the calendar, a cancellation and a no-show look identical: the hour ended up empty. Against the balance they are not remotely the same. One may return the credit and the other may not, and which is which is the studio’s own commercial decision. If that decision is not encoded, every instance gets settled over messages, and two customers asking the same question in the same week can get two different answers.
Capacity carries a parallel distinction. How many people an hour can hold is set by instructor, equipment and the slot together, not by a single seat count. Private sessions and group classes share one calendar while each opens under its own capacity rule.
Finally, two different clocks meet on one record. An invoice arises the moment a package is sold, while usage plays out across weeks of calendar. A sale belongs to an instant, usage to an interval. Held apart, matching what was sold against what was used becomes a month-end reconciliation done by hand.
What carried the difference in three builds
All three builds answer one question against a different object: what does this request attach to? The answer might be a location, an installed unit, a step in a plan, or a package balance. That attachment is what carries the separation each time. The table below sets what held the difference beside what would have happened without it.
| Which two types were split | Which record carries the split | What would have happened otherwise |
|---|---|---|
| Pre-sale site survey × post-installation service request | The survey record centers on location and date, the service record on the installed unit; each request reaches operations in its own flow | At season peak both types pile onto one phone line, sorting happens in conversation, and a visitor who hits a busy line is lost |
| Appointment × treatment plan | The appointment is a record bound to a step of a tooth-by-tooth plan split into sessions; when the plan moves, the calendar moves | The software keeps only the hour; a patient’s position in treatment is reconstructed from the calendar and appointments end up orphaned |
| Booking × cancellation and no-show | The package balance held on the customer record, with an explicit rule for how each event posts to it | Remaining credits are tracked in a notebook, and whether a cancellation returns a credit is answered over messages |
In a system that works, a separation disappears from view; the third column describes the version without it.
The separation belongs in the data model, not the form
Two intake flows do not mean two systems. At Diotek, the survey and service flows run inside one Laravel application alongside the public marketing pages, and the request records share a MySQL schema with the content. Because the schema carries the distinction, a single application produces two already-separated queues.
In practice this meant no second server, no second panel and no second admin interface. What it did require was defining two request types as two records with different fields and different validation rules. Inertia handles page transitions on the client while routing and form validation stay in Laravel. Since the validation rules for both flows live in one place, the type distinction never leaks into client code — the browser does not need to know which field is mandatory for which type.
The seasonal load runs on that same codebase. A Redis cache absorbs the traffic spike at the start of a season, so public pages stop hitting the database on every view, while request submissions still pass through live validation each time. Reads and writes behaving differently is not a reason to split the flows into separate systems.
There is a measurement side to this too. When both types share one record shape, the question of how many surveys came in against how many service requests were opened can only be answered by reading free-text fields. When the record types are distinct, the same question comes straight out of the schema, and nobody has to bolt a classification layer on afterwards to produce a report.
The same principle shows up in billing. In the dental clinic CRM, invoicing does not sit in its own ledger: because the procedure is recorded against the patient, what was treated and what was charged are read from one place. Separation and integrity coexist in one schema — plan, appointment and invoice are distinct records arranged around the same patient.
Building request records that attach to a customer, an installed unit or a plan is the first thing we settle in our CRM work, before any interface exists. Reversed, the order produces a good-looking screen and an incomplete record underneath it.
Can the separation itself be configurable
In all three builds above, we defined the distinction. The next question is whether a business can define it for itself. That is what we are working through in AlgoRandevu, which we are currently building.
Rather than baking slot length and the spacing between bookings into a sector’s assumptions, we are keeping them as product settings, so that changing a rule does not mean changing code. Slot length differs between a clinic, a studio and a workshop, and so does the gap between slots. Compiling that into the product means a new release for every business that books differently.
In the structure we are building, an appointment can attach to a person, a room or a piece of equipment; the business picks its own resource rather than us writing a separate build per sector. We are putting the product on the same Laravel and Inertia base as AlgoKlima and AlgoCar, keeping booking records in MySQL and wiring Redis as the cache layer. Configurability, in other words, does not call for a new stack; it calls for a definition layer. The product is still in development.
When the objects at the center of two types differ fundamentally, as at Diotek with a location on one side and an installed unit on the other, building two explicit flows can still be the right call rather than reducing the difference to a setting. Configurability absorbs variation among rules that resemble each other. It does not squeeze unlike objects under one template. So a configurable appointment definition does not replace the decision to build separate flows; it narrows the set of cases where that decision is needed.
Signs that your business needs separate intake flows
Each of the three builds here started with the same symptoms. If you recognise one of them in your own operation, the record that should carry the distinction has not been built yet.
If what an incoming request actually is can only be established by talking on the phone, classification belongs to a person rather than to a record. That was exactly the situation at Diotek, and it persisted for as long as requests failed to land in writing.
If your software holds the hour but not the step the work is on, appointments are not attached to a plan. In the dental clinic the visible consequence was that the same patient’s status looked different to two members of staff.
If whether a cancellation returns a credit gets answered over messages, cancellation and no-show are being treated as one event. At Pilatesco, moving that rule into the system took the question out of weekly negotiation.
If a business is bending its own process to fit its software, the definition of an appointment has been fixed to one trade.
All four point the same way: information a system does not hold is held by a person, and information held by a person goes missing during holidays, busy weeks and staff turnover. In our management software work, carrying appointments, plans and invoices as separate records inside one application, the first thing written is never a screen — it is these attachments. Screens get redrawn later. What a request attaches to does not.