Skip to content

Commit 19fdb98

Browse files
emoss08claude
andcommitted
feat(payroll): driver settlements, Dash driver portal, and instant pay
Payroll & settlements: - Driver settlement lifecycle (draft -> approve -> post -> paid) with GL posting, batches, workspace, disputes, escrow ledger (49 CFR 376.12(k)), pay advances, recurring earnings/deductions, pay codes, and YTD summaries - Instant pay: payWorkerNow builds an off-cycle settlement from selected accrued events and drives approve/post/paid in one pass; recurring items skipped by default to avoid double-dipping the period settlement - Driver expenses with receipt capture and a back-office review queue that applies approved reimbursements to the driver's open settlement Dash driver portal (second Vite entry at /dash): - Me-rooted GraphQL security model with permission-less Driver role and invitation-based activation - Loads with stop-level arrive/depart, POD/BOL capture, realtime chat, accept/decline with dispatch alerts, and pay estimates - Settlement statements, disputes, escrow/advances, PTO requests, expense submission, DQ-file compliance profile with document upload - Web push (VAPID) notifications and a driver notification center - Credential expiry sweep (Temporal) with driver reminders and compliance alerts; detention candidate alerts for dispatch Dash Control (org feature toggles): - Per-org toggles for every driver-facing capability (load refusals, stop actions, uploads, messaging, pay visibility, expenses, disputes, PTO, contact edits, credential reminders, detention alerts) enforced server-side with realtime propagation to active drivers Also: worker autocomplete owner-operator filter (Contractor type or effective OwnerOperator pay profile), driver_settlement sequence type, overflow tab lists, Office/Driver login split. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dtt7xTGRTj51u87Vgezhh5
1 parent e279303 commit 19fdb98

333 files changed

Lines changed: 150591 additions & 43923 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ Use service-local commands from each module.
2929
- Naming: use descriptive domain-based names (`userservice`, `locationcategory`, `formula_template`); keep files/packages lower-case.
3030
- Keep functions small and explicit; avoid introducing `encoding/json` in Go where project lint rules disallow it.
3131
- Bun ORM: use the [docs](docs/bun/) for help.
32+
- **Repositories**: When writing repositories, always use the generated column helpers in `services/tms/pkg/buncolgen/` — never hand-write column references. Read [docs/bun/buncolgen.md](docs/bun/buncolgen.md) for the full method reference, canonical repository patterns, and regeneration workflow before writing any repository code.
3233
- **Function signatures**: When a Go function has more than ~3-4 parameters, define a params struct instead of a long parameter list.
33-
- **Utility functions go in `shared/`**: Do NOT place generic utility/helper functions inside domain, service, or handler files. The `shared/` directory (`shared/stringutils`, `shared/sliceutils`, `shared/intutils`, etc.) is the home for all reusable utilities. Create new sub-packages there if needed.
34+
- **Utility functions go in `shared/` (backend) and `client/src/lib/` (frontend)**: Do NOT place generic utility/helper functions inside domain, service, or handler files, nor inline in React components/hooks/routes. Backend utilities live in `shared/` (`shared/stringutils`, `shared/sliceutils`, `shared/intutils`, etc.) — create new sub-packages there if needed. Frontend utilities live in `client/src/lib/` (`utils.ts`, `date.ts`, etc.). If a utility that does the same thing already exists, reuse it — never write a duplicate.
3435
- **Performance**: Write efficient, allocation-conscious code. Preallocate slices/maps when sizes are known. Avoid unnecessary copies. Use appropriate data structures for the problem.
3536
- **Code quality**: Prefer clarity and correctness. No dead code, no unused imports, no TODO placeholders left behind. Every function should have a single clear responsibility.
3637

@@ -65,9 +66,15 @@ Use service-local commands from each module.
6566

6667
## Code Quality Non-Negotiables
6768

68-
- **No "v1" or placeholder code**: This is an enterprise TMS application. Implement features fully and completely on the first pass. No stubs, no TODO comments, no "we can improve this later" shortcuts, no simplified versions of what was asked. Handle all edge cases, error states, validation, and integration points. If the feature is complex, that's fine — implement the full complexity.
69+
- **No "v1", "MVP", or placeholder code**: This is an enterprise TMS application. Never write a "v1", "MVP", or simplified version of what was asked. Implement features fully and completely on the first pass. No stubs, no TODO comments, no "we can improve this later" shortcuts. Handle all edge cases, error states, validation, and integration points. If the feature is complex, that's fine — implement the full complexity.
70+
- **Secure and bug-free**: All code must be secure — no injection vectors, no unvalidated input, no leaked secrets, proper authorization checks — and free of bugs. Follow DRY & SOLID principles in every implementation.
6971
- Never deviate from the established code style in the repository. Match the patterns already in use.
7072
- Do not introduce new dependencies without justification.
7173
- Do not leave commented-out code, TODO comments, or placeholder implementations.
7274
- Prefer the simplest correct solution. Do not over-engineer.
7375
- All error paths must be handled explicitly — no swallowed errors.
76+
77+
## Do Not
78+
79+
- Do not run high usage tasks that will max out CPU, Disk and/or memory usage.
80+
- Do not run mockery against the entire codebase — manually adjust mocks in the codebase.

CLAUDE.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,12 @@ Supports nested paths (`user.address.street`) and array indices (`items[0].name`
149149
## Code Style
150150

151151
### General Principles
152-
- **Production-grade, fully featured code**: This is an enterprise application. Every feature must be implemented completely — no stubs, no "v1" shortcuts, no "can be improved later" placeholders. If a feature needs error handling, edge cases, validation, proper UX states, or integration with existing systems, implement all of it in the first pass. Do not simplify or reduce scope unless explicitly told to.
152+
- **Production-grade, fully featured code**: This is an enterprise application. Never write "v1", "MVP", or simplified versions of a feature. Every feature must be implemented completely — no stubs, no shortcuts, no "can be improved later" placeholders. If a feature needs error handling, edge cases, validation, proper UX states, or integration with existing systems, implement all of it in the first pass. Do not simplify or reduce scope unless explicitly told to.
153+
- **Secure and correct**: All code must be secure (no injection vectors, no unvalidated input, no leaked secrets, proper authz checks) and free of bugs — handle every error path and edge case explicitly.
153154
- **DRY**: Do not repeat yourself — extract shared logic rather than duplicating code
154155
- **SOLID**: Follow SOLID principles strictly (single responsibility, open/closed, Liskov substitution, interface segregation, dependency inversion)
155156
- **Performance**: Write the most efficient and performant code possible — avoid unnecessary allocations, prefer stack over heap, minimize copies, use appropriate data structures
156-
- **Utility functions**: Place reusable utility functions in the `shared/` package (e.g., `shared/stringutils`, `shared/sliceutils`, `shared/intutils`). Do NOT scatter utility/helper functions in domain or service files. If a utility package doesn't exist for the category, create one in `shared/`
157+
- **Utility functions**: Never duplicate a utility — if a function that does the same thing already exists, reuse it. Backend: place reusable utilities in the `shared/` package (e.g., `shared/stringutils`, `shared/sliceutils`, `shared/intutils`); do NOT scatter utility/helper functions in domain or service files; if a utility package doesn't exist for the category, create one in `shared/`. Frontend: place reusable utilities in `client/src/lib/` (`utils.ts`, `date.ts`, etc.); do NOT define them inline in components, hooks, or routes
157158

158159
### Go
159160
- Follow the [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md) as the baseline for all Go code
@@ -179,3 +180,9 @@ Supports nested paths (`user.address.street`) and array indices (`items[0].name`
179180
## Bun ORM
180181

181182
For help with Bun ORM, look in the [docs](docs/bun/).
183+
When writing repositories, always use the generated column helpers in `services/tms/pkg/buncolgen/` — never hand-write column references. Read [docs/bun/buncolgen.md](docs/bun/buncolgen.md) for the full method reference, canonical repository patterns, and regeneration workflow before writing any repository code.
184+
185+
## DO NOT
186+
- **Processes**: Do not run high usage tasks that will max out CPU, Disk and/or memory usage.
187+
- **Mockery**: Do not run mockery against the entire codebase — manually adjust mocks in the codebase.
188+

client/dash.html

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta
6+
name="viewport"
7+
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
8+
/>
9+
<link rel="icon" type="image/x-icon" href="/logo.ico" />
10+
<link rel="manifest" href="/dash.webmanifest" />
11+
<meta name="theme-color" content="#09090b" />
12+
<meta name="apple-mobile-web-app-capable" content="yes" />
13+
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
14+
<meta name="apple-mobile-web-app-title" content="Dash" />
15+
<title>Dash</title>
16+
<meta name="description" content="Your driver portal: loads, settlements, and pay." />
17+
18+
<meta property="og:title" content="Dash" />
19+
<meta
20+
property="og:description"
21+
content="Your driver portal: loads, settlements, and pay."
22+
/>
23+
<meta property="og:type" content="website" />
24+
</head>
25+
<body class="overscroll-none antialiased">
26+
<div id="root"></div>
27+
<script type="module" src="/src/dash/main.tsx"></script>
28+
</body>
29+
</html>

client/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"@fontsource-variable/inter": "^5.2.8",
4141
"@fontsource/geist-mono": "^5.2.8",
4242
"@fontsource/ibm-plex-mono": "^5.2.7",
43+
"@foony/realtime": "^0.14.0",
4344
"@graphql-typed-document-node/core": "^3.2.0",
4445
"@hookform/resolvers": "^5.4.0",
4546
"@lezer/highlight": "^1.2.3",
@@ -48,14 +49,14 @@
4849
"@nivo/colors": "^0.99.0",
4950
"@number-flow/react": "^0.6.1",
5051
"@radix-ui/react-collapsible": "^1.1.16",
52+
"@shadcn/react": "^0.2.1",
5153
"@tailwindcss/vite": "^4.3.2",
5254
"@tanstack/react-hotkeys": "^0.10.0",
5355
"@tanstack/react-query": "^5.101.2",
5456
"@tanstack/react-table": "^8.21.3",
5557
"@tanstack/react-virtual": "^3.14.6",
5658
"@uiw/react-codemirror": "^4.25.11",
5759
"@vis.gl/react-google-maps": "^1.9.0",
58-
"@foony/realtime": "^0.14.0",
5960
"chrono-node": "^2.9.1",
6061
"class-variance-authority": "^0.7.1",
6162
"clsx": "^2.1.1",

client/pnpm-lock.yaml

Lines changed: 31 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/public/dash-sw.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/* Dash service worker: receives web push notifications and routes clicks. */
2+
3+
self.addEventListener("install", () => {
4+
self.skipWaiting();
5+
});
6+
7+
self.addEventListener("activate", (event) => {
8+
event.waitUntil(self.clients.claim());
9+
});
10+
11+
self.addEventListener("push", (event) => {
12+
let payload = { title: "Dash", body: "", link: "/dash" };
13+
if (event.data) {
14+
try {
15+
payload = { ...payload, ...event.data.json() };
16+
} catch {
17+
payload.body = event.data.text();
18+
}
19+
}
20+
21+
event.waitUntil(
22+
self.registration.showNotification(payload.title, {
23+
body: payload.body,
24+
icon: "/logo.ico",
25+
badge: "/logo.ico",
26+
tag: payload.eventId || undefined,
27+
data: { link: payload.link || "/dash" },
28+
}),
29+
);
30+
});
31+
32+
self.addEventListener("notificationclick", (event) => {
33+
event.notification.close();
34+
const link = (event.notification.data && event.notification.data.link) || "/dash";
35+
const url = new URL(link, self.location.origin).href;
36+
37+
event.waitUntil(
38+
self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clients) => {
39+
for (const client of clients) {
40+
if (client.url.startsWith(self.location.origin) && "focus" in client) {
41+
client.navigate(url);
42+
return client.focus();
43+
}
44+
}
45+
return self.clients.openWindow(url);
46+
}),
47+
);
48+
});

client/public/dash.webmanifest

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "Dash — Driver Portal",
3+
"short_name": "Dash",
4+
"description": "Your driver portal: loads, settlements, and pay.",
5+
"start_url": "/dash",
6+
"scope": "/dash",
7+
"display": "standalone",
8+
"orientation": "portrait",
9+
"background_color": "#09090b",
10+
"theme_color": "#09090b",
11+
"icons": [
12+
{
13+
"src": "/logo.ico",
14+
"sizes": "48x48",
15+
"type": "image/x-icon"
16+
}
17+
]
18+
}

client/src/components/accounting/journal-line-items-editor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export function JournalLineItemsEditor({ className }: JournalLineItemsEditorProp
118118
onValueCommit={clearOpposite(index, "debitAmount")}
119119
/>
120120
</td>
121-
<td className="px-1 py-1.5 pt-4 justify-center flex items-center text-center">
121+
<td className="flex items-center justify-center px-1 py-1.5 pt-4 text-center">
122122
<Button
123123
type="button"
124124
variant="ghost"

client/src/components/autocomplete-fields.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -562,12 +562,24 @@ export function ServiceTypeAutocompleteField<T extends FieldValues>({
562562
}
563563

564564
export function WorkerAutocompleteField<T extends FieldValues>({
565+
ownerOperatorsOnly,
565566
...props
566-
}: BaseAutocompleteFieldProps<GraphQLSelectOption, T>) {
567+
}: BaseAutocompleteFieldProps<GraphQLSelectOption, T> & {
568+
/**
569+
* Narrows the picker to owner-operators: Contractor-type workers, plus any
570+
* driver whose effective pay profile carries the OwnerOperator
571+
* classification.
572+
*/
573+
ownerOperatorsOnly?: boolean;
574+
}) {
567575
return (
568576
<AutocompleteField<GraphQLSelectOption, T>
569577
link="/workers/select-options/"
570-
graphql={workerSelectOptionsGraphQL}
578+
graphql={
579+
ownerOperatorsOnly
580+
? { ...workerSelectOptionsGraphQL, filters: { ownerOperatorsOnly: true } }
581+
: workerSelectOptionsGraphQL
582+
}
571583
popoutLink="/workers"
572584
getOptionValue={(option) => option.id || ""}
573585
getDisplayValue={(option) => option.label}
@@ -1039,7 +1051,9 @@ export function ControlledShipmentAutocompleteField({
10391051
renderOption={(option) => (
10401052
<EDIOptionStack
10411053
primary={option.label}
1042-
secondary={selectOptionMetaString(option, "bol") || selectOptionMetaString(option, "status")}
1054+
secondary={
1055+
selectOptionMetaString(option, "bol") || selectOptionMetaString(option, "status")
1056+
}
10431057
/>
10441058
)}
10451059
{...props}

0 commit comments

Comments
 (0)