Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

LoadPartner TMS - An open source Transportation Management System for freight brokers built with Laravel + Inertia.js + React + TypeScript.

## Common Development Commands

### Environment Setup
```bash
# Set up with Laravel Sail
sail up -d
sail artisan migrate
sail npm install
sail artisan key:generate
sail npm run dev

# Fresh test data
sail artisan dev:refresh
```

### Development Workflow
```bash
# Run all dev services (server, queue, logs, vite)
composer run dev

# Individual commands
sail artisan serve
sail npm run dev
sail artisan queue:listen --tries=1
sail artisan pail --timeout=0
```

### Testing & Quality
```bash
# Run tests (uses Pest)
sail artisan test
# or
sail pest

# Run static analysis
sail artisan dev:check # Runs PHPStan + IDE helper generation
vendor/bin/phpstan analyse

# Lint frontend
npm run lint
```

### Build & Production
```bash
# Build frontend assets
npm run build

# Link storage for file uploads
sail artisan storage:link
```

## Architecture Overview

### Laravel Actions Pattern
- All business logic organized in `app/Actions/` by domain
- Uses `lorisleiva/laravel-actions` package
- Each action has `handle()`, `asController()`, `rules()`, `authorize()` methods
- Actions serve as both controllers and reusable business logic

### Multi-Tenant Architecture
- All models use `HasOrganization` trait for automatic scoping
- `OrganizationScope` provides global filtering
- Use `current_organization_id()` helper for context

### State Machine Pattern
- Shipments use `spatie/laravel-model-states`
- States: Pending → Booked → Dispatched → AtPickup → InTransit → AtDelivery → Delivered
- State transitions trigger events for side effects

### Frontend Structure
- React + TypeScript with Inertia.js for SPA-like experience
- Components organized by domain in `resources/js/Components/`
- UI components in `resources/js/Components/ui/`
- Pages in `resources/js/Pages/`

### Key Models & Relationships
- `Organization` - Multi-tenant container
- `Shipment` - Core business entity with state machine
- `Carrier` - Transportation providers
- `Customer` - Freight customers
- `Contact` - Polymorphic contacts system
- `Document` - Polymorphic file attachments
- `Note` - Polymorphic notes system

## Development Patterns

### Adding New Features
1. Create action in appropriate domain directory
2. Add enum values if needed (sync with `Permission::syncToDatabase()`)
3. Create policy for authorization
4. Add tests in corresponding test directories
5. Create frontend components following existing patterns

### Permissions System
- Add permissions to `app/Enums/Permission.php`
- Include entry in `label()` method
- Create migration calling `App\Enums\Permission::syncToDatabase()`

### Event-Driven Updates
- Use event bus for real-time updates between components
- Emit events: `emit('event-name-' + id)`
- Subscribe: `subscribe('event-name-' + id, callback)`

### Organization Defaults
- Modify `CreateOrUpdateOrganizationDefaults` action
- Consider both migration for existing orgs and action updates for new ones

## Database & Storage

### Database
- Uses SQLite for development, PostgreSQL for production
- Migrations in `database/migrations/`
- Seeders for test data in `database/seeders/`

### File Storage
- Supports both local and S3 storage
- Configured via `FILESYSTEM_DISK` environment variable
- Uses polymorphic `Document` model for file attachments

## Testing

### Test Structure
- Unit tests in `tests/Unit/`
- Feature tests in `tests/Feature/`
- Uses Pest testing framework
- Factory classes for model creation

### Test Users
Available via `sail artisan dev:refresh`:
- `admin@test.com` / `password` (admin)
- `user@test.com` / `password` (regular user)

## Key Dependencies

### Backend
- `lorisleiva/laravel-actions` - Business logic actions
- `spatie/laravel-model-states` - State machine
- `spatie/laravel-permission` - Authorization
- `inertiajs/inertia-laravel` - Frontend bridge
- `laravel/cashier` - Subscription billing

### Frontend
- `@inertiajs/react` - Inertia.js React adapter
- `@tanstack/react-table` - Data tables
- `react-hook-form` - Form management
- `@radix-ui/*` - UI components
- `tailwindcss` - Styling

## Code Style & Standards

### PHP
- Follow PSR-12 coding standards
- Use PHP 8.2+ features including enums
- Type hints required for all methods
- PHPStan level 5 static analysis

### TypeScript/React
- Strict TypeScript configuration
- Props interfaces for all components
- Use React hooks patterns
- ESLint + Prettier for code formatting

## Troubleshooting

### Common Issues
- Run `sail artisan storage:link` if file uploads fail
- Check organization context if data not appearing
- Verify permissions if authorization fails
- Use event bus for component updates, not direct prop passing

### Development Environment
- All services run via Docker Compose
- Access application at `http://localhost`
- Mailpit at `http://localhost:8025` for email testing
- Database accessible via standard Laravel tools
72 changes: 72 additions & 0 deletions _ide_helper_actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ class SavePayables
class SaveReceivables
{
}
namespace App\Actions\Audit;

/**
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(string $type, int $id)
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(string $type, int $id)
* @method static \Illuminate\Foundation\Bus\PendingDispatch dispatch(string $type, int $id)
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchIf(bool $boolean, string $type, int $id)
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchUnless(bool $boolean, string $type, int $id)
* @method static dispatchSync(string $type, int $id)
* @method static dispatchNow(string $type, int $id)
* @method static dispatchAfterResponse(string $type, int $id)
* @method static mixed run(string $type, int $id)
*/
class GetAuditLinkedData
{
}
namespace App\Actions\Carriers;

/**
Expand Down Expand Up @@ -116,6 +132,20 @@ class FmcsaDOTLookup
class FmcsaNameLookup
{
}
/**
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Carriers\Carrier $carrier)
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Carriers\Carrier $carrier)
* @method static \Illuminate\Foundation\Bus\PendingDispatch dispatch(\App\Models\Carriers\Carrier $carrier)
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchIf(bool $boolean, \App\Models\Carriers\Carrier $carrier)
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchUnless(bool $boolean, \App\Models\Carriers\Carrier $carrier)
* @method static dispatchSync(\App\Models\Carriers\Carrier $carrier)
* @method static dispatchNow(\App\Models\Carriers\Carrier $carrier)
* @method static dispatchAfterResponse(\App\Models\Carriers\Carrier $carrier)
* @method static mixed run(\App\Models\Carriers\Carrier $carrier)
*/
class GetCarrierAuditHistory
{
}
/**
* @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)
* @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)
Expand Down Expand Up @@ -248,6 +278,20 @@ class CreateCustomerFacility
class DeleteCustomerFacility
{
}
/**
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Customers\Customer $customer)
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Customers\Customer $customer)
* @method static \Illuminate\Foundation\Bus\PendingDispatch dispatch(\App\Models\Customers\Customer $customer)
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchIf(bool $boolean, \App\Models\Customers\Customer $customer)
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchUnless(bool $boolean, \App\Models\Customers\Customer $customer)
* @method static dispatchSync(\App\Models\Customers\Customer $customer)
* @method static dispatchNow(\App\Models\Customers\Customer $customer)
* @method static dispatchAfterResponse(\App\Models\Customers\Customer $customer)
* @method static mixed run(\App\Models\Customers\Customer $customer)
*/
class GetCustomerAuditHistory
{
}
/**
* @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)
* @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)
Expand Down Expand Up @@ -498,6 +542,20 @@ class GenerateRateConfirmation
class CreateFacility
{
}
/**
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Facility $facility)
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Facility $facility)
* @method static \Illuminate\Foundation\Bus\PendingDispatch dispatch(\App\Models\Facility $facility)
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchIf(bool $boolean, \App\Models\Facility $facility)
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchUnless(bool $boolean, \App\Models\Facility $facility)
* @method static dispatchSync(\App\Models\Facility $facility)
* @method static dispatchNow(\App\Models\Facility $facility)
* @method static dispatchAfterResponse(\App\Models\Facility $facility)
* @method static mixed run(\App\Models\Facility $facility)
*/
class GetFacilityAuditHistory
{
}
/**
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Facility $facility, ?string $name = null, ?int $location_id = null)
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Facility $facility, ?string $name = null, ?int $location_id = null)
Expand Down Expand Up @@ -802,6 +860,20 @@ class DispatchShipment
class GetShipmentAccounting
{
}
/**
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Shipments\Shipment $shipment)
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Shipments\Shipment $shipment)
* @method static \Illuminate\Foundation\Bus\PendingDispatch dispatch(\App\Models\Shipments\Shipment $shipment)
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchIf(bool $boolean, \App\Models\Shipments\Shipment $shipment)
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchUnless(bool $boolean, \App\Models\Shipments\Shipment $shipment)
* @method static dispatchSync(\App\Models\Shipments\Shipment $shipment)
* @method static dispatchNow(\App\Models\Shipments\Shipment $shipment)
* @method static dispatchAfterResponse(\App\Models\Shipments\Shipment $shipment)
* @method static mixed run(\App\Models\Shipments\Shipment $shipment)
*/
class GetShipmentAuditHistory
{
}
/**
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Shipments\Shipment $shipment, ?\App\Services\Shipments\ShipmentStateService $stateService = null)
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Shipments\Shipment $shipment, ?\App\Services\Shipments\ShipmentStateService $stateService = null)
Expand Down
Loading