Client Portal
The client portal is where a client organisation’s people sign in. It lives at /client and is a
separate avenue from the org console, project admin and the supplier portal — a client never enters
an internal screen, and internal staff never enter the client portal.
Registering a client organisation and binding its projects happens on the head contractor’s side and is documented under Clients. This page describes what the client themselves sees.
Signing in
A client contact signs in on the same login page as everyone else. Where they land is derived from
the avenues their role carries, not from their email domain or any hardcoded rule: the client
role carries web_client, and web_client lands on /client.
If an account somehow carries both a client avenue and an office avenue, the office console wins —
web_client is deliberately last in the landing order.
What the client sees
They land on their projects, as a table. Opening a project name leads to that project’s diaries.
| Column | Meaning |
|---|---|
| Project | The project name as the head contractor named it |
| Head contractor | The organisation that granted the link |
| Status | The project’s own status |
| Linked since | When the project was bound to the client’s organisation |
The table lists exactly the projects that have been explicitly linked to the client’s organisation, and nothing else. A project belonging to the same head contractor that has not been linked does not appear. Unbinding a project removes it from this list immediately, and suspending the client relationship removes all of them.
If no projects have been linked yet, the page says so plainly. That is a deliberate empty state, not an error.
No commercial information is served to the client. The response the portal receives carries no rate, cost, total, contract id, contract name or agreement type — those fields are not omitted from the screen, they are absent from the payload. A client cannot reach them by reading the network tab.
Diaries
Opening a project shows its diaries, newest first.
The three views
| View | Shows |
|---|---|
| All | Every diary the client may see |
| Submitted for Approval | Diaries the site team has submitted, awaiting approval |
| Engineer Approved | Diaries the engineer has approved, and confirmed ones |
Each view carries a count, and the two narrower counts always add up to All.
The view and the page are part of the address, so a refreshed page comes back on the same view and a copied link opens on it too.
What a row shows
Date, diary number, shift, supervisor, site location, how many activities the diary records, how many photos it carries, how many dockets it carries, and its status. Nothing else.
The photo count counts photos that have finished processing. A photo still being processed is not counted, because it is not yet a photo the client can look at.
The docket count counts settled dockets — submitted, engineer-approved or confirmed. Drafts are never counted, for the same reason draft diaries are never listed. It counts dockets recorded directly against the diary; a master subcontractor docket that is linked to several diaries at once is not counted on the row, though it does appear in the docket table when the diary is opened. The row’s number is therefore a floor rather than an exact figure, and it is deliberately the cheap one — a count on a list is a glance, and the diary itself is where the record is read.
Drafts are never shown
A diary the site team is still writing is a draft, and a client never sees one — not on a view, not in a count, and not in the data the browser receives. This is not a filter applied to the screen: the request itself cannot ask for a draft, and the query that answers it cannot return one. Asking for an unrecognised view is refused rather than answered.
A diary that is sent back to draft leaves the client’s list until it is submitted again, at which point it returns under Submitted for Approval.
What is deliberately absent
The client’s diary list carries no money of any kind, and none of the internal record of who did what and when — no submitted, reviewed, confirmed or reverted timestamps and no names attached to them. Those fields are not hidden on the screen; they are absent from the data sent to the browser.
The row’s docket count is the one workflow-shaped number a client does get, and it is defined for them rather than borrowed: it counts settled dockets only, so it never reveals how much work is still being written up.
Opening a diary
Clicking a row opens that diary, read-only. The client reads the site record; they never act on it.
A subscribed contact also arrives here from the View Diary link in the diary-submitted email — see Diary submission notifications. The link opens this page, never the internal diary screen.
What the page shows, in order
| Section | What it carries |
|---|---|
| Back to diaries | Returns to the project’s diary list |
| Previous / next day | The same supervisor’s diary on the nearest earlier or later day, same shift; never a draft — see Previous & Next Day |
| Header | The date in full, the diary number, shift, supervisor and the diary’s status |
| Shift information | Diary number, shift, supervisor, site location, project location and the approval timestamps |
| Additional information | Every custom field the project defines for diaries, with its recorded value |
| Handover notes | The crew’s written handover for the shift |
| Weather | The recorded forecast for the shift — high, low, wet bulb, wind, rainfall, humidity |
| Activities | Each activity with its cost code, target quantity and unit, and logged quantity and unit |
| Work carried out | The shift’s written activity description |
| Changes after publish | The notes recorded when the linked prestart changed after it was published |
| Issues | Each open issue’s number, heading and status |
| Photos | The shift’s photos, opening full size when clicked |
| Files | Documents attached to the diary |
| Dockets | The shift’s settled dockets — see Dockets on a diary below |
A section with nothing in it says so plainly rather than disappearing.
There is no action bar. No edit, save, submit, review, confirm, revert, delete, print, raise issue, voice note, add activity or upload control renders anywhere on the page, there is no comment box, and there is no “dockets still pending” banner — how much work is still being written up is internal.
Dockets on a diary
The dockets appear in three sections, in this order: Labour & plant, Subcontractor, Materials. A section with no dockets is left out rather than shown empty.
The labour and subcontractor tables lead with day works:
| Column | What it carries |
|---|---|
| Day works item | What was done, as recorded on the docket |
| Quantity | How much of it — or Pending when the quantity has not been entered |
| Unit | The unit that quantity is measured in |
| Applies to | The crew or item it applies to, or Docket for a whole-docket entry |
Then the docket’s own facts: docket number, supplier, supervisor, start, end, break, shift duration, personnel, plant and status.
Day works lead because that is what a client reads a site record to find: what was done, and how much of it. The internal table leads with resourcing and ends in a total because it exists to be priced. Same diary, different question.
A docket carrying several day-works lines stays one row — the lines stack inside the cell, so skim-counting the table counts dockets and not lines.
Pending is not zero. A day-works line can be carried onto a docket from a worker’s assignment
before anyone has entered a quantity. That reads Pending. Showing it as 0 would tell a client
a claim of nothing was made, which is a different statement from no claim yet.
A docket with no day works still appears, with a dash in each of the four leading columns. A gap in the record is worse than a blank cell.
Materials lead with a lines count. Where the internal materials table shows a Total, the client’s shows Lines — how many delivery lines the docket carries. The Total column is not hidden or blanked; it does not exist on this table, and no figure behind it is sent.
Rows do not open anything. There is no docket detail view for a client: no sheet, no drawer, no navigation. Clicking a row does nothing at all.
Only settled dockets appear. A docket is visible once it is submitted, engineer-approved or confirmed. Draft dockets are internal workflow state and are never listed and never counted — and this is not a filter on the screen: the query that answers the request cannot return one.
No money, at any depth. No rate, amount, total, cost or price appears on any column, and none is in the response either — not on the docket, not on a day-works line, not on a worker. Day-works lines carry a description, a unit and a quantity, and nothing else. Those fields are never selected from the database rather than being selected and then cleared, so there is no figure on the wire for anything downstream to reveal.
The same day works, internally
The internal diary now shows a Day works column too, beside the existing Total Amount column, which is unchanged. That column carries the same description, quantity and unit and no rate or line total of its own — the diary’s docket table has no pricing permission behind it, so putting day-works money there would widen what an internal viewer sees rather than narrow it. Day-works rates stay where they already were, on the docket itself and on the Day Works workbench.
The three exclusions
Three things a client never receives, and the distinction matters:
- Money. No rate, cost, total or amount of any kind, on any section.
- Comments. The diary’s comment thread is not shown, and is not fetched at all.
- Issue cost impact and extension of time. Issues appear with their heading and status only.
All three are enforced on the server, in the response itself — not by hiding parts of the screen. Those fields are never selected from the database, never placed on the response and never sent. A client reading the network tab sees exactly what they see on the page. This matters beyond tidiness: the mobile client app consumes this same response, so anything the response carried would be carried there too.
Every custom field is visible to the client
There is no per-field visibility switch, by decision. Every custom field a project defines for diaries is shown to the client, with its value.
That includes a table custom field’s numeric Totals row. A totals row carries no currency symbol, but it is a sum — and a sum can be a dollar figure in everything but formatting.
A project’s owner must not put commercially sensitive content in a diary custom field. A field created for internal margin tracking, a rate lookup or a subcontractor’s pricing will be shown to every client linked to that project. If information must not reach a client, it does not belong on a diary custom field.
Deleting the field is the remedy, and it works retrospectively. If a field turns out to hold content a client should not read, delete it from the project’s custom fields. The client stops seeing it on every diary that ever carried it, not just on future ones — the values already recorded against a deleted field are removed from the client’s data as well as from the page. Only fields the project currently defines are ever sent.
Handover notes are shown
Shift handover notes are crew-to-crew free text, and they are visible to the client. They are part of the site record the client is entitled to read.
Because they are free text, nothing inside them can be redacted — there is no way to show part of a note. Site teams should write handover notes knowing the client reads them.
Photo and file links expire
Photos and attached documents are served through signed links that stop working after fifteen minutes. Each link carries a signature; altering any part of it makes it invalid immediately.
Two consequences worth knowing:
- A link copied out of the page and pasted elsewhere stops working shortly afterwards. That is deliberate: a link forwarded outside the client’s organisation never becomes a permanent public URL.
- If a page has been left open a long while, a photo may need a refresh to load again.
A small number of older attachments were stored in a way that cannot be signed. Those render as a named file with no link, rather than as an unsigned one. The name is still shown, so the record of what was attached stays complete.
Which diaries can be opened
Exactly the ones that appear on the list. A draft, a diary in a project that is not linked, a diary belonging to a different head contractor and a diary that does not exist all answer not found, in the same words — a client cannot tell which of the four they hit. A diary sent back to draft stops opening until it is submitted again.
Who is refused, and how
| Situation | Result |
|---|---|
A client visits /org, /admin or /dashboard | Refused; they are sent to the no-web-access page |
An internal staff member visits /client | Refused; they are sent to the no-web-access page |
A signed-out visitor requests /client | Sent to the login page |
| A non-client organisation calls the portal API | Not found — deliberately not “forbidden” |
The last row is intentional. Answering “forbidden” would confirm to an outside caller that the endpoint exists and only their organisation type disqualifies them, which turns the portal into a way to probe organisation types from outside the tenancy boundary. Errors inside the product stay exact; this one boundary does not enumerate.
The first two rows are the same outcome because they are the same mechanism, applied symmetrically: the set of avenues an account holds is checked before the page is reached, and an account that does not hold the avenue a path requires is sent to the no-web-access page.
There is a second, independent refusal inside the portal itself, for the cases where that first
check cannot decide: a session minted before the avenue list existed, one whose avenue list has been
tampered with, or an account that genuinely holds the client avenue but whose organisation is not a
client organisation. In all of those the portal is reached and refuses in place — an
access-restricted screen renders and the address stays on /client.
That second refusal renders rather than redirects, deliberately. A redirect from inside the portal shell could bounce a user between the login page and the portal indefinitely, so a failure inside the shell always shows a screen.
How access is decided
Two independent things must be true, and neither is inferred:
- The caller’s organisation is a client organisation. Asserted once, at the portal’s API mount, so every endpoint added later inherits it.
- The project is explicitly linked. Every visible project is one row in the client link tables, with a live, active link and a live, active project binding. There is no org-wide client visibility to fall back on; an absent row means an absent project.
The caller’s organisation always comes from the verified session. No portal endpoint accepts an organisation from the request, so there is nothing for a caller to substitute.
Asking for a project by name in the address — as the diary list does — changes nothing about that. The project is checked against the link rows before a single diary is read, and a project that is not linked is answered exactly as a project that does not exist: not found, in the same words. A client cannot use the portal to discover what else a head contractor is building.
Mobile Client Flow
The same portal is on the phone. A client contact signs in on the Lacroo app with the same email and password they use on the web and lands in the Client Flow — a first-class flow beside the Field and Office flows, not a web page in a shell.
Four screens:
| Screen | What it shows |
|---|---|
| Projects | One card per linked project: project name, head contractor, status, linked since. |
| Diaries | One project’s diaries, with the same three views as the web portal. |
| Diary | One diary, read-only, with the same sections and the same day-works-led dockets. |
| Profile | Name, email, role, and the client organisation. |
Lists are native cards, not tables. A table that needs sideways scrolling to read is a desktop idiom; a phone shows the same facts stacked, and a card is tappable everywhere rather than only on a link.
The mobile flow reads exactly what the web portal reads. It calls the same client-portal endpoints, so everything on this page — the three views, drafts never appearing, money never appearing, comments never appearing, issues without cost impact or extension of time, every custom field being visible, expiring photo and file links — is true on the phone for the same reason it is true on the web: the server never sends those things to a client. Nothing is hidden by the app.
There is nothing to edit. The flow makes no changes of any kind: no approve, no comment, no raise-issue, no photo upload. A client reads the site record.
Where an internal user lands. An account carrying both an office avenue and a client avenue goes to the office flow, on the phone exactly as on the web. If such an account did reach a client screen, every request would be answered not found — the client endpoints serve client organisations only, and they are the boundary, not the app.
Custom fields on a phone read the same as on the web
Repeating the rule above because it is the one with a consequence a project owner must act on: every custom field on a diary is visible to the linked client, on the web and on the phone alike. A custom field holding commercially sensitive content must not be added to a project that has a client link. Deleting the field remains the remedy, and it works retrospectively.
Two other places staff commentary must not go, for the same reason: comments are never shown to a client on either surface, so a note meant for internal readers belongs in a comment rather than in a handover note or an activity description; and issues reach the client without their cost impact or extension-of-time, so a client sees that an issue exists and what state it is in, never what it is worth.
What is not here yet
Inviting a client contact by email, so they set their own password rather than being seeded, is the outstanding piece.
Notifications are not wired to the mobile flow: a client is not sent a push when a diary is approved, and there is no notification-settings screen in the app yet.