Skip to content

Commit b0dab71

Browse files
authored
Merge pull request #375 from loadpartner/nick/audit-logging
Audit logging
2 parents cdde800 + f60b95f commit b0dab71

40 files changed

Lines changed: 3851 additions & 1598 deletions

CLAUDE.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
LoadPartner TMS - An open source Transportation Management System for freight brokers built with Laravel + Inertia.js + React + TypeScript.
8+
9+
## Common Development Commands
10+
11+
### Environment Setup
12+
```bash
13+
# Set up with Laravel Sail
14+
sail up -d
15+
sail artisan migrate
16+
sail npm install
17+
sail artisan key:generate
18+
sail npm run dev
19+
20+
# Fresh test data
21+
sail artisan dev:refresh
22+
```
23+
24+
### Development Workflow
25+
```bash
26+
# Run all dev services (server, queue, logs, vite)
27+
composer run dev
28+
29+
# Individual commands
30+
sail artisan serve
31+
sail npm run dev
32+
sail artisan queue:listen --tries=1
33+
sail artisan pail --timeout=0
34+
```
35+
36+
### Testing & Quality
37+
```bash
38+
# Run tests (uses Pest)
39+
sail artisan test
40+
# or
41+
sail pest
42+
43+
# Run static analysis
44+
sail artisan dev:check # Runs PHPStan + IDE helper generation
45+
vendor/bin/phpstan analyse
46+
47+
# Lint frontend
48+
npm run lint
49+
```
50+
51+
### Build & Production
52+
```bash
53+
# Build frontend assets
54+
npm run build
55+
56+
# Link storage for file uploads
57+
sail artisan storage:link
58+
```
59+
60+
## Architecture Overview
61+
62+
### Laravel Actions Pattern
63+
- All business logic organized in `app/Actions/` by domain
64+
- Uses `lorisleiva/laravel-actions` package
65+
- Each action has `handle()`, `asController()`, `rules()`, `authorize()` methods
66+
- Actions serve as both controllers and reusable business logic
67+
68+
### Multi-Tenant Architecture
69+
- All models use `HasOrganization` trait for automatic scoping
70+
- `OrganizationScope` provides global filtering
71+
- Use `current_organization_id()` helper for context
72+
73+
### State Machine Pattern
74+
- Shipments use `spatie/laravel-model-states`
75+
- States: Pending → Booked → Dispatched → AtPickup → InTransit → AtDelivery → Delivered
76+
- State transitions trigger events for side effects
77+
78+
### Frontend Structure
79+
- React + TypeScript with Inertia.js for SPA-like experience
80+
- Components organized by domain in `resources/js/Components/`
81+
- UI components in `resources/js/Components/ui/`
82+
- Pages in `resources/js/Pages/`
83+
84+
### Key Models & Relationships
85+
- `Organization` - Multi-tenant container
86+
- `Shipment` - Core business entity with state machine
87+
- `Carrier` - Transportation providers
88+
- `Customer` - Freight customers
89+
- `Contact` - Polymorphic contacts system
90+
- `Document` - Polymorphic file attachments
91+
- `Note` - Polymorphic notes system
92+
93+
## Development Patterns
94+
95+
### Adding New Features
96+
1. Create action in appropriate domain directory
97+
2. Add enum values if needed (sync with `Permission::syncToDatabase()`)
98+
3. Create policy for authorization
99+
4. Add tests in corresponding test directories
100+
5. Create frontend components following existing patterns
101+
102+
### Permissions System
103+
- Add permissions to `app/Enums/Permission.php`
104+
- Include entry in `label()` method
105+
- Create migration calling `App\Enums\Permission::syncToDatabase()`
106+
107+
### Event-Driven Updates
108+
- Use event bus for real-time updates between components
109+
- Emit events: `emit('event-name-' + id)`
110+
- Subscribe: `subscribe('event-name-' + id, callback)`
111+
112+
### Organization Defaults
113+
- Modify `CreateOrUpdateOrganizationDefaults` action
114+
- Consider both migration for existing orgs and action updates for new ones
115+
116+
## Database & Storage
117+
118+
### Database
119+
- Uses SQLite for development, PostgreSQL for production
120+
- Migrations in `database/migrations/`
121+
- Seeders for test data in `database/seeders/`
122+
123+
### File Storage
124+
- Supports both local and S3 storage
125+
- Configured via `FILESYSTEM_DISK` environment variable
126+
- Uses polymorphic `Document` model for file attachments
127+
128+
## Testing
129+
130+
### Test Structure
131+
- Unit tests in `tests/Unit/`
132+
- Feature tests in `tests/Feature/`
133+
- Uses Pest testing framework
134+
- Factory classes for model creation
135+
136+
### Test Users
137+
Available via `sail artisan dev:refresh`:
138+
- `admin@test.com` / `password` (admin)
139+
- `user@test.com` / `password` (regular user)
140+
141+
## Key Dependencies
142+
143+
### Backend
144+
- `lorisleiva/laravel-actions` - Business logic actions
145+
- `spatie/laravel-model-states` - State machine
146+
- `spatie/laravel-permission` - Authorization
147+
- `inertiajs/inertia-laravel` - Frontend bridge
148+
- `laravel/cashier` - Subscription billing
149+
150+
### Frontend
151+
- `@inertiajs/react` - Inertia.js React adapter
152+
- `@tanstack/react-table` - Data tables
153+
- `react-hook-form` - Form management
154+
- `@radix-ui/*` - UI components
155+
- `tailwindcss` - Styling
156+
157+
## Code Style & Standards
158+
159+
### PHP
160+
- Follow PSR-12 coding standards
161+
- Use PHP 8.2+ features including enums
162+
- Type hints required for all methods
163+
- PHPStan level 5 static analysis
164+
165+
### TypeScript/React
166+
- Strict TypeScript configuration
167+
- Props interfaces for all components
168+
- Use React hooks patterns
169+
- ESLint + Prettier for code formatting
170+
171+
## Troubleshooting
172+
173+
### Common Issues
174+
- Run `sail artisan storage:link` if file uploads fail
175+
- Check organization context if data not appearing
176+
- Verify permissions if authorization fails
177+
- Use event bus for component updates, not direct prop passing
178+
179+
### Development Environment
180+
- All services run via Docker Compose
181+
- Access application at `http://localhost`
182+
- Mailpit at `http://localhost:8025` for email testing
183+
- Database accessible via standard Laravel tools

_ide_helper_actions.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ class SavePayables
4444
class SaveReceivables
4545
{
4646
}
47+
namespace App\Actions\Audit;
48+
49+
/**
50+
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(string $type, int $id)
51+
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(string $type, int $id)
52+
* @method static \Illuminate\Foundation\Bus\PendingDispatch dispatch(string $type, int $id)
53+
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchIf(bool $boolean, string $type, int $id)
54+
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchUnless(bool $boolean, string $type, int $id)
55+
* @method static dispatchSync(string $type, int $id)
56+
* @method static dispatchNow(string $type, int $id)
57+
* @method static dispatchAfterResponse(string $type, int $id)
58+
* @method static mixed run(string $type, int $id)
59+
*/
60+
class GetAuditLinkedData
61+
{
62+
}
4763
namespace App\Actions\Carriers;
4864

4965
/**
@@ -116,6 +132,20 @@ class FmcsaDOTLookup
116132
class FmcsaNameLookup
117133
{
118134
}
135+
/**
136+
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Carriers\Carrier $carrier)
137+
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Carriers\Carrier $carrier)
138+
* @method static \Illuminate\Foundation\Bus\PendingDispatch dispatch(\App\Models\Carriers\Carrier $carrier)
139+
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchIf(bool $boolean, \App\Models\Carriers\Carrier $carrier)
140+
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchUnless(bool $boolean, \App\Models\Carriers\Carrier $carrier)
141+
* @method static dispatchSync(\App\Models\Carriers\Carrier $carrier)
142+
* @method static dispatchNow(\App\Models\Carriers\Carrier $carrier)
143+
* @method static dispatchAfterResponse(\App\Models\Carriers\Carrier $carrier)
144+
* @method static mixed run(\App\Models\Carriers\Carrier $carrier)
145+
*/
146+
class GetCarrierAuditHistory
147+
{
148+
}
119149
/**
120150
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Carriers\Carrier $carrier, ?string $name = null, ?string $mc_number = null, ?string $dot_number = null, ?int $physical_location_id = null, ?string $contact_email = null, ?string $contact_phone = null)
121151
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Carriers\Carrier $carrier, ?string $name = null, ?string $mc_number = null, ?string $dot_number = null, ?int $physical_location_id = null, ?string $contact_email = null, ?string $contact_phone = null)
@@ -248,6 +278,20 @@ class CreateCustomerFacility
248278
class DeleteCustomerFacility
249279
{
250280
}
281+
/**
282+
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Customers\Customer $customer)
283+
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Customers\Customer $customer)
284+
* @method static \Illuminate\Foundation\Bus\PendingDispatch dispatch(\App\Models\Customers\Customer $customer)
285+
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchIf(bool $boolean, \App\Models\Customers\Customer $customer)
286+
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchUnless(bool $boolean, \App\Models\Customers\Customer $customer)
287+
* @method static dispatchSync(\App\Models\Customers\Customer $customer)
288+
* @method static dispatchNow(\App\Models\Customers\Customer $customer)
289+
* @method static dispatchAfterResponse(\App\Models\Customers\Customer $customer)
290+
* @method static mixed run(\App\Models\Customers\Customer $customer)
291+
*/
292+
class GetCustomerAuditHistory
293+
{
294+
}
251295
/**
252296
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Customers\Customer $customer, ?string $name = null, ?int $net_pay_days = null, ?int $billing_location_id = null, ?string $dba_name = null, ?string $invoice_number_schema = null, ?int $billing_contact_id = null)
253297
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Customers\Customer $customer, ?string $name = null, ?int $net_pay_days = null, ?int $billing_location_id = null, ?string $dba_name = null, ?string $invoice_number_schema = null, ?int $billing_contact_id = null)
@@ -498,6 +542,20 @@ class GenerateRateConfirmation
498542
class CreateFacility
499543
{
500544
}
545+
/**
546+
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Facility $facility)
547+
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Facility $facility)
548+
* @method static \Illuminate\Foundation\Bus\PendingDispatch dispatch(\App\Models\Facility $facility)
549+
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchIf(bool $boolean, \App\Models\Facility $facility)
550+
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchUnless(bool $boolean, \App\Models\Facility $facility)
551+
* @method static dispatchSync(\App\Models\Facility $facility)
552+
* @method static dispatchNow(\App\Models\Facility $facility)
553+
* @method static dispatchAfterResponse(\App\Models\Facility $facility)
554+
* @method static mixed run(\App\Models\Facility $facility)
555+
*/
556+
class GetFacilityAuditHistory
557+
{
558+
}
501559
/**
502560
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Facility $facility, ?string $name = null, ?int $location_id = null)
503561
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Facility $facility, ?string $name = null, ?int $location_id = null)
@@ -802,6 +860,20 @@ class DispatchShipment
802860
class GetShipmentAccounting
803861
{
804862
}
863+
/**
864+
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Shipments\Shipment $shipment)
865+
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Shipments\Shipment $shipment)
866+
* @method static \Illuminate\Foundation\Bus\PendingDispatch dispatch(\App\Models\Shipments\Shipment $shipment)
867+
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchIf(bool $boolean, \App\Models\Shipments\Shipment $shipment)
868+
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchUnless(bool $boolean, \App\Models\Shipments\Shipment $shipment)
869+
* @method static dispatchSync(\App\Models\Shipments\Shipment $shipment)
870+
* @method static dispatchNow(\App\Models\Shipments\Shipment $shipment)
871+
* @method static dispatchAfterResponse(\App\Models\Shipments\Shipment $shipment)
872+
* @method static mixed run(\App\Models\Shipments\Shipment $shipment)
873+
*/
874+
class GetShipmentAuditHistory
875+
{
876+
}
805877
/**
806878
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Shipments\Shipment $shipment, ?\App\Services\Shipments\ShipmentStateService $stateService = null)
807879
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Shipments\Shipment $shipment, ?\App\Services\Shipments\ShipmentStateService $stateService = null)

0 commit comments

Comments
 (0)