Compare commits
39 Commits
@ -0,0 +1,5 @@
|
||||
[Dolphin]
|
||||
Timestamp=2024,11,17,16,40,18.95
|
||||
Version=4
|
||||
ViewMode=1
|
||||
VisibleRoles=Icons_text,Icons_size
|
@ -0,0 +1,69 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=base64:vRgghlbIdXQxXIEvgUArbI9FURhgdyqx3LDXDwHYSmA=
|
||||
APP_DEBUG=true
|
||||
APP_TIMEZONE=UTC
|
||||
APP_URL=http://localhost
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
# APP_MAINTENANCE_STORE=database
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=mysql
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=testing
|
||||
DB_USERNAME=sail
|
||||
DB_PASSWORD=password
|
||||
|
||||
SESSION_DRIVER=database
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
CACHE_STORE=database
|
||||
CACHE_PREFIX=
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=redis
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=mailpit
|
||||
MAIL_PORT=1025
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
|
||||
SCOUT_DRIVER=meilisearch
|
||||
MEILISEARCH_HOST=http://meilisearch:7700
|
||||
|
||||
MEILISEARCH_NO_ANALYTICS=false
|
@ -0,0 +1,9 @@
|
||||
const {join} = require('path');
|
||||
|
||||
/**
|
||||
* @type {import("puppeteer").Configuration}
|
||||
*/
|
||||
module.exports = {
|
||||
// Changes the cache location for Puppeteer.
|
||||
cacheDirectory: join(__dirname, '.cache', 'puppeteer'),
|
||||
};
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum InvoiceStatus: string implements HasColor, HasIcon, HasLabel
|
||||
{
|
||||
case UNPAID = 'Not paid';
|
||||
case PAID = 'Paid';
|
||||
case VOID = 'Void';
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getColor(): string|array|null
|
||||
{
|
||||
return match ($this) {
|
||||
self::UNPAID => 'warning',
|
||||
self::PAID => 'success',
|
||||
self::VOID => 'gray'
|
||||
};
|
||||
}
|
||||
|
||||
public function getIcon(): ?string
|
||||
{
|
||||
return match ($this) {
|
||||
self::UNPAID => 'lucide-circle-x',
|
||||
self::PAID => 'lucide-circle-check',
|
||||
self::VOID => 'lucide-circle-slash',
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Filament\Support\Contracts\HasIcon;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum OrderAttributes: string implements HasIcon, HasLabel
|
||||
{
|
||||
case new_art = 'New Art';
|
||||
case repeat = 'Repeat';
|
||||
case rush = 'Rush';
|
||||
case event = 'Event';
|
||||
case digitizing = 'Digitizing';
|
||||
case garments = 'Garments';
|
||||
case supplied_file = 'Customer Supplied File';
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getIcon(): ?string
|
||||
{
|
||||
return match ($this) {
|
||||
self::new_art => 'lucide-brush',
|
||||
self::repeat => 'lucide-files',
|
||||
self::rush => 'lucide-bell-ring',
|
||||
self::event => 'lucide-calendar-range',
|
||||
self::digitizing => 'lucide-computer',
|
||||
self::garments => 'lucide-shirt',
|
||||
self::supplied_file => 'lucide-file-check',
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\ContactResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ContactResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateContact extends CreateRecord
|
||||
{
|
||||
protected static string $resource = ContactResource::class;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\ContactResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ContactResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditContact extends EditRecord
|
||||
{
|
||||
protected static string $resource = ContactResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\ContactResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ContactResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListContacts extends ListRecords
|
||||
{
|
||||
protected static string $resource = ContactResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\CustomerReportResource\Pages;
|
||||
use App\Models\Customer;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CustomerReportResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Customer::class;
|
||||
|
||||
protected static ?string $navigationGroup = 'Reports';
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
|
||||
|
||||
protected static ?string $navigationLabel = 'Customer Sales';
|
||||
|
||||
protected static ?int $navigationSort = 2;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
//
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('company_name')
|
||||
->label('Customer')
|
||||
->sortable()
|
||||
->searchable()
|
||||
->extraHeaderAttributes(['class' => 'w-full']),
|
||||
|
||||
Tables\Columns\TextColumn::make('subtotal')
|
||||
->money('usd')
|
||||
->alignRight()
|
||||
->getStateUsing(function (Table $table, Model $record) {
|
||||
return $record->getSubtotalAttribute(
|
||||
$table->getFilter('created_at')->getState()['created_at'],
|
||||
$table->getFilter('created_until')->getState()['created_until']
|
||||
);
|
||||
}),
|
||||
|
||||
Tables\Columns\TextColumn::make('gst')
|
||||
->label('GST')
|
||||
->money('usd')
|
||||
->alignRight()
|
||||
->getStateUsing(function (Table $table, Model $record) {
|
||||
return $record->getGstAttribute(
|
||||
$table->getFilter('created_at')->getState()['created_at'],
|
||||
$table->getFilter('created_until')->getState()['created_until']
|
||||
);
|
||||
}),
|
||||
|
||||
Tables\Columns\TextColumn::make('pst')
|
||||
->label('PST')
|
||||
->money('usd')
|
||||
->alignRight()
|
||||
->getStateUsing(function (Table $table, Model $record) {
|
||||
return $record->getPstAttribute(
|
||||
$table->getFilter('created_at')->getState()['created_at'],
|
||||
$table->getFilter('created_until')->getState()['created_until']
|
||||
);
|
||||
}),
|
||||
|
||||
Tables\Columns\TextColumn::make('total')
|
||||
->money('usd')
|
||||
->weight('bold')
|
||||
->alignRight()
|
||||
->getStateUsing(function (Table $table, Model $record) {
|
||||
return $record->getTotalAttribute(
|
||||
$table->getFilter('created_at')->getState()['created_at'],
|
||||
$table->getFilter('created_until')->getState()['created_until']
|
||||
);
|
||||
}),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\Filter::make('created_at')
|
||||
->form([
|
||||
DatePicker::make('created_at')
|
||||
->label('From date'),
|
||||
]),
|
||||
|
||||
Tables\Filters\Filter::make('created_until')
|
||||
->form([
|
||||
DatePicker::make('created_until')
|
||||
->label('Until date'),
|
||||
]),
|
||||
])
|
||||
->actions([
|
||||
])
|
||||
->bulkActions([
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListCustomerReports::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\CustomerReportResource\Pages;
|
||||
|
||||
use App\Filament\Resources\CustomerReportResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateCustomerReport extends CreateRecord
|
||||
{
|
||||
protected static string $resource = CustomerReportResource::class;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\CustomerReportResource\Pages;
|
||||
|
||||
use App\Filament\Resources\CustomerReportResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditCustomerReport extends EditRecord
|
||||
{
|
||||
protected static string $resource = CustomerReportResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\CustomerReportResource\Pages;
|
||||
|
||||
use App\Filament\Resources\CustomerReportResource;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListCustomerReports extends ListRecords
|
||||
{
|
||||
protected static string $resource = CustomerReportResource::class;
|
||||
|
||||
protected static ?string $title = 'Customer Reports';
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
// Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\CustomerResource\Pages;
|
||||
use App\Filament\Resources\CustomerResource\RelationManagers\ContactsRelationManager;
|
||||
use App\Filament\Resources\CustomerResource\RelationManagers\ShippingEntriesRelationManager;
|
||||
use App\Models\Customer;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class CustomerResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Customer::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-building-office';
|
||||
|
||||
protected static ?string $navigationGroup = 'Management';
|
||||
|
||||
protected static ?int $navigationSort = 1;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Section::make([
|
||||
TextInput::make('company_name'),
|
||||
TextInput::make('phone'),
|
||||
TextInput::make('shipping_address_line_1'),
|
||||
TextInput::make('shipping_address_line_2'),
|
||||
TextInput::make('billing_address_line_1'),
|
||||
TextInput::make('billing_address_line_2'),
|
||||
])->columns(2),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('company_name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
// TextColumn::make('shipping_address'),
|
||||
TextColumn::make('phone'),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
// RelationGroup::make('Relations', [
|
||||
ContactsRelationManager::class,
|
||||
ShippingEntriesRelationManager::class,
|
||||
// ]),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListCustomers::route('/'),
|
||||
'create' => Pages\CreateCustomer::route('/create'),
|
||||
'edit' => Pages\EditCustomer::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\CustomerResource\Pages;
|
||||
|
||||
use App\Filament\Resources\CustomerResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateCustomer extends CreateRecord
|
||||
{
|
||||
protected static string $resource = CustomerResource::class;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\CustomerResource\Pages;
|
||||
|
||||
use App\Filament\Resources\CustomerResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditCustomer extends EditRecord
|
||||
{
|
||||
protected static string $resource = CustomerResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
//todo: make report
|
||||
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\CustomerResource\Pages;
|
||||
|
||||
use App\Filament\Resources\CustomerResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListCustomers extends ListRecords
|
||||
{
|
||||
protected static string $resource = CustomerResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\CustomerResource\RelationManagers;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ContactsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'contacts';
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
TextInput::make('first_name'),
|
||||
TextInput::make('last_name'),
|
||||
TextInput::make('email')
|
||||
->email(),
|
||||
TextInput::make('phone'),
|
||||
TextInput::make('notes'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('id')
|
||||
->columns([
|
||||
TextColumn::make('full_name'),
|
||||
TextColumn::make('email'),
|
||||
TextColumn::make('phone'),
|
||||
TextColumn::make('notes')
|
||||
->extraHeaderAttributes(['class' => 'w-full']),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->headerActions([
|
||||
Tables\Actions\CreateAction::make(),
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
Tables\Actions\DeleteAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
// Tables\Actions\BulkActionGroup::make([
|
||||
// Tables\Actions\DeleteBulkAction::make(),
|
||||
// ]),
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\CustomerResource\RelationManagers;
|
||||
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ShippingEntriesRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'ShippingEntries';
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('id')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
]);
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('courier'),
|
||||
Tables\Columns\TextColumn::make('account_title'),
|
||||
Tables\Columns\TextColumn::make('account_username')
|
||||
->label('Username'),
|
||||
Tables\Columns\TextColumn::make('account_password')
|
||||
->label('Password'),
|
||||
Tables\Columns\TextColumn::make('info_needed'),
|
||||
// ->extraHeaderAttributes(['class' => 'w-full']),
|
||||
Tables\Columns\TextColumn::make('notes'),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->headerActions([
|
||||
Tables\Actions\CreateAction::make(),
|
||||
])
|
||||
->actions([
|
||||
// Tables\Actions\EditAction::make(),
|
||||
// Tables\Actions\DeleteAction::make(),
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Enums\InvoiceStatus;
|
||||
use App\Filament\Resources\InvoiceReportResource\Pages;
|
||||
use App\Models\Order;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Grouping\Group;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class InvoiceReportResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Order::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
|
||||
|
||||
protected static ?string $navigationGroup = 'Reports';
|
||||
|
||||
protected static ?string $navigationLabel = 'Invoice Reports';
|
||||
|
||||
protected static ?int $navigationSort = 2;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
//
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('invoice.date')
|
||||
->label('Date')
|
||||
->date('Y-m-d'),
|
||||
|
||||
Tables\Columns\TextColumn::make('invoice.internal_id')
|
||||
->color('primary')
|
||||
->fontFamily('mono'),
|
||||
|
||||
Tables\Columns\TextColumn::make('customer_po')
|
||||
->color('code')
|
||||
->weight('bold')
|
||||
->extraHeaderAttributes(['class' => 'w-full']),
|
||||
|
||||
Tables\Columns\TextColumn::make('total_service_price')
|
||||
->label('Subtotal')
|
||||
->alignRight()
|
||||
->money(),
|
||||
|
||||
Tables\Columns\TextColumn::make('pst')
|
||||
->label('PST')
|
||||
->alignRight()
|
||||
->getStateUsing(fn (Order $record) => $record->invoice->pst ? $record->total_service_price * 0.07 : 0.00)
|
||||
->formatStateUsing(function ($state) {
|
||||
return $state == 0.00 ? '-' : '$'.$state;
|
||||
}),
|
||||
|
||||
Tables\Columns\TextColumn::make('gst')
|
||||
->label('GST')
|
||||
->getStateUsing(fn (Order $record) => $record->total_service_price * 0.05)
|
||||
->alignRight()
|
||||
->money(),
|
||||
|
||||
Tables\Columns\TextColumn::make('invoice.total')
|
||||
->label('Total')
|
||||
->getStateUsing(function (Order $record) {
|
||||
$total = $record->total_service_price * 1.05;
|
||||
|
||||
if ($record->invoice->pst) {
|
||||
$total += $record->total_service_price * 0.07;
|
||||
}
|
||||
|
||||
return $total;
|
||||
})
|
||||
->alignRight()
|
||||
->money(),
|
||||
|
||||
Tables\Columns\TextColumn::make('invoice.status')
|
||||
->badge(InvoiceStatus::class),
|
||||
])
|
||||
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('customer')
|
||||
->relationship('customer', 'company_name')
|
||||
->preload()
|
||||
->searchable()
|
||||
->placeholder('Select a customer...')
|
||||
->selectablePlaceholder(false)
|
||||
->query(function (array $data, Builder $query): Builder {
|
||||
return $query->where('orders.customer_id', $data['value'] ?? '-1');
|
||||
}),
|
||||
|
||||
Tables\Filters\Filter::make('date_from')
|
||||
->form([DatePicker::make('date_from')])
|
||||
->query(function (Builder $query, array $data): Builder {
|
||||
return $query->when($data['date_from'], function (Builder $query, $date) {
|
||||
return $query->whereHas('invoice', fn ($query) => $query->whereDate('date', '>=', $date));
|
||||
});
|
||||
}),
|
||||
|
||||
Tables\Filters\Filter::make('date_until')
|
||||
->form([DatePicker::make('date_until')])
|
||||
->query(function (Builder $query, array $data): Builder {
|
||||
return $query->when($data['date_until'], function (Builder $query, $date) {
|
||||
return $query->whereHas('invoice', fn ($query) => $query->whereDate('date', '<=', $date));
|
||||
});
|
||||
}),
|
||||
|
||||
Tables\Filters\SelectFilter::make('invoice_status')
|
||||
->options(InvoiceStatus::class)
|
||||
->query(function (Builder $query, array $data): Builder {
|
||||
return $query->when($data['value'], fn (Builder $query, $value) => $query->whereHas('invoice', fn (Builder $query) => $query->where('status', $value)));
|
||||
}),
|
||||
|
||||
], layout: Tables\Enums\FiltersLayout::AboveContent)
|
||||
->hiddenFilterIndicators()
|
||||
|
||||
->actions([])
|
||||
->defaultGroup(
|
||||
Group::make('date')
|
||||
->getKeyFromRecordUsing(fn (Order $record): string => $record->invoice->date->format('Y-m-0'))
|
||||
->getTitleFromRecordUsing(fn (Order $record): string => $record->invoice->date->format('F Y'))
|
||||
->orderQueryUsing(function (Builder $query) {
|
||||
|
||||
return $query->join('invoices', 'orders.invoice_id', '=', 'invoices.id')
|
||||
->orderBy('invoices.date', 'desc');
|
||||
})
|
||||
->titlePrefixedWithLabel(false),
|
||||
);
|
||||
}
|
||||
|
||||
public static function getEloquentQuery(): \Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
return Order::query()
|
||||
->has('invoice');
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListInvoiceReports::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\InvoiceReportResource\Pages;
|
||||
|
||||
use App\Filament\Resources\InvoiceReportResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateInvoiceReport extends CreateRecord
|
||||
{
|
||||
protected static string $resource = InvoiceReportResource::class;
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\InvoiceReportResource\Pages;
|
||||
|
||||
use App\Filament\Resources\InvoiceReportResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditInvoiceReport extends EditRecord
|
||||
{
|
||||
protected static string $resource = InvoiceReportResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\ViewAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\InvoiceReportResource\Pages;
|
||||
|
||||
use App\Filament\Resources\InvoiceReportResource;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListInvoiceReports extends ListRecords
|
||||
{
|
||||
protected static string $resource = InvoiceReportResource::class;
|
||||
|
||||
protected static ?string $title = 'Invoice Reports';
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('generateReport')
|
||||
->label('Make Report')
|
||||
->icon('lucide-printer'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\InvoiceReportResource\Pages;
|
||||
|
||||
use App\Filament\Resources\InvoiceReportResource;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewInvoiceReport extends ViewRecord
|
||||
{
|
||||
protected static string $resource = InvoiceReportResource::class;
|
||||
|
||||
protected static ?string $title = 'View Invoice Report';
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Enums\InvoiceStatus;
|
||||
use App\Filament\Resources\InvoiceResource\Pages;
|
||||
use App\Filament\Resources\InvoiceResource\RelationManagers\OrdersRelationManager;
|
||||
use App\Filament\Resources\InvoiceResource\RelationManagers\ProductServicesRelationManager;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Invoice;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Split;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class InvoiceResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Invoice::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'lucide-receipt-text';
|
||||
|
||||
protected static ?string $navigationGroup = 'Production';
|
||||
|
||||
protected static ?int $navigationSort = 2;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Section::make([
|
||||
Grid::make(2)
|
||||
->schema([
|
||||
Select::make('customer_id')
|
||||
->required()
|
||||
->label('Customer')
|
||||
->options(Customer::all()->pluck('company_name', 'id'))
|
||||
->reactive()
|
||||
->searchable()
|
||||
->disabledOn('edit')
|
||||
->columnSpan(2),
|
||||
|
||||
Split::make([
|
||||
DatePicker::make('date')
|
||||
->required()
|
||||
->default(today()),
|
||||
DatePicker::make('due_date'),
|
||||
])
|
||||
->columnSpan(2),
|
||||
Select::make('status')
|
||||
->options(InvoiceStatus::class)
|
||||
->searchable()
|
||||
->required()
|
||||
->default(InvoiceStatus::UNPAID),
|
||||
])->columnSpan(2),
|
||||
|
||||
Grid::make(1)
|
||||
->schema([
|
||||
ToggleButtons::make('gst')
|
||||
->boolean()
|
||||
->default(true)
|
||||
->inline()
|
||||
->colors([
|
||||
'true' => 'info',
|
||||
'false' => 'info',
|
||||
]),
|
||||
|
||||
ToggleButtons::make('pst')
|
||||
->boolean()
|
||||
->default(false)
|
||||
->inline()
|
||||
->colors([
|
||||
'true' => 'info',
|
||||
'false' => 'info',
|
||||
]),
|
||||
])->columnSpan(1),
|
||||
])
|
||||
->columns(3)
|
||||
->columnSpan(3),
|
||||
])->columns(3);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('internal_id')
|
||||
->label('ID')
|
||||
->fontFamily('mono')
|
||||
->color('primary')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('customer.company_name')
|
||||
->sortable()
|
||||
->extraHeaderAttributes(['class' => 'w-full'])
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->label('Created')
|
||||
->date()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('subtotal')
|
||||
->money('USD')
|
||||
->alignRight(),
|
||||
Tables\Columns\TextColumn::make('gst_amount')
|
||||
->label('GST')
|
||||
->money('USD')
|
||||
->alignRight(),
|
||||
Tables\Columns\TextColumn::make('pst_amount')
|
||||
->label('PST')
|
||||
->formatStateUsing(function ($state) {
|
||||
return $state == 0.00 ? '-' : '$'.$state;
|
||||
})
|
||||
->alignRight(),
|
||||
Tables\Columns\TextColumn::make('total')
|
||||
->money('USD')
|
||||
->weight('bold')
|
||||
->alignRight(),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->badge(InvoiceStatus::class)
|
||||
->sortable(),
|
||||
])
|
||||
|
||||
->filters([
|
||||
Tables\Filters\Filter::make('created_at')
|
||||
->form([
|
||||
DatePicker::make('created_from')
|
||||
->label('From date'),
|
||||
DatePicker::make('created_until')
|
||||
->label('Until date'),
|
||||
])
|
||||
->query(function (Builder $query, array $data): Builder {
|
||||
return $query
|
||||
->when(
|
||||
$data['created_from'],
|
||||
fn (Builder $query, $date): Builder => $query->whereDate('date', '>=', $date),
|
||||
)
|
||||
->when(
|
||||
$data['created_until'],
|
||||
fn (Builder $query, $date): Builder => $query->whereDate('date', '<=', $date),
|
||||
);
|
||||
}),
|
||||
|
||||
Tables\Filters\SelectFilter::make('status')
|
||||
->options(InvoiceStatus::class),
|
||||
|
||||
], )
|
||||
|
||||
->groups([
|
||||
'status',
|
||||
])
|
||||
|
||||
->defaultSort('created_at', 'desc')
|
||||
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
//todo: generate report pdf
|
||||
])
|
||||
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkAction::make('Mark as paid')
|
||||
->action(function (Collection $records) {
|
||||
$records->each->setStatus(InvoiceStatus::PAID);
|
||||
Notification::make()
|
||||
->title(count($records).' item(s) saved successfully')
|
||||
->success()
|
||||
->send();
|
||||
})
|
||||
->icon('lucide-circle-check')
|
||||
->deselectRecordsAfterCompletion(),
|
||||
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\BulkAction::make('Mark as unpaid')
|
||||
->action(function (Collection $records) {
|
||||
$records->each->setStatus(InvoiceStatus::UNPAID);
|
||||
Notification::make()
|
||||
->title(count($records).' item(s) saved successfully')
|
||||
->success()
|
||||
->send();
|
||||
})
|
||||
->icon('lucide-circle-x')
|
||||
->deselectRecordsAfterCompletion(),
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
])
|
||||
->label('Other actions'),
|
||||
])
|
||||
->selectCurrentPageOnly();
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
OrdersRelationManager::class,
|
||||
ProductServicesRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListInvoices::route('/'),
|
||||
'create' => Pages\CreateInvoice::route('/create'),
|
||||
'edit' => Pages\EditInvoice::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\InvoiceResource\Pages;
|
||||
|
||||
use App\Filament\Resources\InvoiceResource;
|
||||
use App\Models\Invoice;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CreateInvoice extends CreateRecord
|
||||
{
|
||||
protected static string $resource = InvoiceResource::class;
|
||||
|
||||
protected function handleRecordCreation(array $data): Model
|
||||
{
|
||||
$invoice = Invoice::create($data);
|
||||
$invoice->calculateTotals();
|
||||
|
||||
return $invoice;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\InvoiceResource\Pages;
|
||||
|
||||
use App\Filament\Resources\InvoiceResource;
|
||||
use App\Models\Invoice;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditInvoice extends EditRecord
|
||||
{
|
||||
protected static string $resource = InvoiceResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('print')
|
||||
->icon('lucide-printer')
|
||||
->url(fn (Invoice $record) => route('invoice.pdf', $record))
|
||||
->openUrlInNewTab(),
|
||||
Actions\DeleteAction::make()
|
||||
->icon('lucide-trash-2'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\InvoiceResource\Pages;
|
||||
|
||||
use App\Filament\Resources\InvoiceResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListInvoices extends ListRecords
|
||||
{
|
||||
protected static string $resource = InvoiceResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\InvoiceResource\RelationManagers;
|
||||
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class OrdersRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'orders';
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('customer_po')
|
||||
->required()
|
||||
->maxLength(100),
|
||||
]);
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('customer_po')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('customer_po')
|
||||
->color('code')
|
||||
->weight('bold')
|
||||
->extraHeaderAttributes(['class' => 'w-full']),
|
||||
Tables\Columns\TextColumn::make('total_product_quantity')
|
||||
->label('Total QTY')
|
||||
->alignRight(),
|
||||
Tables\Columns\TextColumn::make('total_service_price')
|
||||
->alignRight()
|
||||
->label('Total price')
|
||||
->money('usd'),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->headerActions([
|
||||
Tables\Actions\AssociateAction::make()
|
||||
->multiple()
|
||||
->preloadRecordSelect()
|
||||
->recordSelectOptionsQuery(fn (Builder $query) => $query->where('customer_id', $this->ownerRecord->customer->id))
|
||||
->after(function () {
|
||||
$this->ownerRecord->calculateTotals();
|
||||
}),
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\DissociateAction::make()
|
||||
->after(function () {
|
||||
$this->ownerRecord->calculateTotals();
|
||||
}),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DissociateBulkAction::make(),
|
||||
]),
|
||||
])
|
||||
->inverseRelationship('invoice');
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\InvoiceResource\RelationManagers;
|
||||
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ProductServicesRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'ProductServices';
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('id')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
]);
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('id')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('order.internal_po')
|
||||
->label('WO')
|
||||
->color('primary')
|
||||
->fontFamily('mono')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('order.customer_po')
|
||||
->label('PO')
|
||||
->color('code')
|
||||
->weight('bold')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('serviceType.name')
|
||||
->label('Type')
|
||||
->weight('bold')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('service_details'),
|
||||
Tables\Columns\TextColumn::make('amount')
|
||||
->label('QTY'),
|
||||
Tables\Columns\TextColumn::make('amount_price')
|
||||
->label('Rate')
|
||||
->prefix('$'),
|
||||
Tables\Columns\TextColumn::make('price')
|
||||
->label('Amount')
|
||||
->prefix('$'),
|
||||
])
|
||||
->filters([
|
||||
])
|
||||
->headerActions([
|
||||
])
|
||||
->actions([
|
||||
])
|
||||
->bulkActions([
|
||||
])
|
||||
->defaultPaginationPageOption('all');
|
||||
}
|
||||
}
|
@ -0,0 +1,322 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Enums\OrderAttributes;
|
||||
use App\Enums\OrderStatus;
|
||||
use App\Enums\OrderType;
|
||||
use App\Filament\Resources\OrderResource\Pages;
|
||||
use App\Models\Contact;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Order;
|
||||
use App\Models\ServiceType;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Split;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Guava\FilamentClusters\Forms\Cluster;
|
||||
use Icetalker\FilamentTableRepeater\Forms\Components\TableRepeater;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class OrderResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Order::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'lucide-shopping-cart';
|
||||
|
||||
protected static ?string $navigationGroup = 'Production';
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form->schema([
|
||||
Section::make([
|
||||
|
||||
Grid::make(1)
|
||||
->schema([
|
||||
Select::make('order_type')
|
||||
->required()
|
||||
->options(OrderType::class)
|
||||
->searchable(),
|
||||
|
||||
Split::make([
|
||||
Select::make('customer_id')
|
||||
->required()
|
||||
->label('Customer')
|
||||
->options(Customer::all()->pluck('company_name', 'id'))
|
||||
->reactive()
|
||||
->searchable(),
|
||||
|
||||
Select::make('contact_id')
|
||||
->label('Contact')
|
||||
->options(fn ($get): array => Contact::where('customer_id', $get('customer_id') ?? null)
|
||||
->get()
|
||||
->pluck('full_name', 'id')
|
||||
->toArray())
|
||||
->searchable(),
|
||||
]),
|
||||
|
||||
TextInput::make('customer_po')
|
||||
->required()
|
||||
->label('Customer PO'),
|
||||
|
||||
Split::make([
|
||||
DatePicker::make('order_date')
|
||||
->required()
|
||||
->default(today()),
|
||||
DatePicker::make('due_date')
|
||||
->required()
|
||||
->default(today()->add('10 days')),
|
||||
]),
|
||||
|
||||
Textarea::make('notes')
|
||||
->rows(3),
|
||||
|
||||
])->columnSpan(1),
|
||||
|
||||
Grid::make(1)
|
||||
->schema([
|
||||
ToggleButtons::make('status')
|
||||
->required()
|
||||
->default(OrderStatus::DRAFT->value)
|
||||
->options(OrderStatus::class)
|
||||
->inline(),
|
||||
|
||||
ToggleButtons::make('order_attributes')
|
||||
->options(OrderAttributes::class)
|
||||
->multiple()
|
||||
->inline(),
|
||||
|
||||
ToggleButtons::make('printed')
|
||||
->boolean()
|
||||
->default(false)
|
||||
->inline(),
|
||||
ToggleButtons::make('pre_production')
|
||||
->label('Pre-production')
|
||||
->default(false)
|
||||
->boolean()
|
||||
->inline()
|
||||
->colors([
|
||||
'true' => 'info',
|
||||
'false' => 'info',
|
||||
]),
|
||||
|
||||
])->columnSpan(1),
|
||||
])->columns(2),
|
||||
|
||||
TableRepeater::make('order_products')
|
||||
->label('Garments')
|
||||
->schema([
|
||||
TextInput::make('sku'),
|
||||
TextInput::make('product_name')
|
||||
->required(),
|
||||
TextInput::make('color'),
|
||||
Cluster::make([
|
||||
TextInput::make('xs')
|
||||
->placeholder('xs'),
|
||||
TextInput::make('s')
|
||||
->placeholder('s'),
|
||||
TextInput::make('m')
|
||||
->placeholder('m'),
|
||||
TextInput::make('l')
|
||||
->placeholder('l'),
|
||||
TextInput::make('xl')
|
||||
->placeholder('xl'),
|
||||
TextInput::make('2xl')
|
||||
->placeholder('2xl'),
|
||||
TextInput::make('3xl')
|
||||
->placeholder('3xl'),
|
||||
TextInput::make('osfa')
|
||||
->placeholder('osfa'),
|
||||
])
|
||||
->label('Sizes'),
|
||||
])
|
||||
->reorderable()
|
||||
->cloneable(),
|
||||
|
||||
Repeater::make('services')
|
||||
->label('Product Services')
|
||||
->schema([
|
||||
Grid::make(19)
|
||||
->schema([
|
||||
Select::make('serviceType')
|
||||
->options(ServiceType::all()->pluck('name', 'id'))
|
||||
->columnSpan(2)
|
||||
->placeholder('Select...')
|
||||
->searchable(),
|
||||
TextInput::make('placement')
|
||||
->columnSpan(3),
|
||||
TextInput::make('serviceFileName')
|
||||
->columnSpan(3)
|
||||
->label('Logo Name'),
|
||||
TextInput::make('serviceFileSetupNumber')
|
||||
->label('Setup')
|
||||
->columnSpan(1),
|
||||
|
||||
Cluster::make([
|
||||
TextInput::make('serviceFileWidth')
|
||||
->prefix('w'),
|
||||
TextInput::make('serviceFileHeight')
|
||||
->prefix('h'),
|
||||
])
|
||||
->label('Dimensions')
|
||||
->columnSpan(4),
|
||||
|
||||
TextInput::make('amount')
|
||||
->label('Quantity')
|
||||
->live()
|
||||
->reactive()
|
||||
->afterStateUpdated(function ($state, Get $get, Set $set) {
|
||||
$set('total_price', ($get('amount_price') * $state ?? 0));
|
||||
})
|
||||
->afterStateHydrated(function ($state, Get $get, Set $set) {
|
||||
$set('total_price', ($get('amount_price') * $state ?? 0));
|
||||
})
|
||||
->prefix('#')
|
||||
->columnSpan(2),
|
||||
|
||||
TextInput::make('amount_price')
|
||||
->prefix('$')
|
||||
->reactive()
|
||||
->afterStateUpdated(function ($state, Get $get, Set $set) {
|
||||
$set('total_price', ($get('amount') * $state ?? 0));
|
||||
})
|
||||
->afterStateHydrated(function ($state, Get $get, Set $set) {
|
||||
$set('total_price', ($get('amount') * $state ?? 0));
|
||||
})
|
||||
->columnSpan(2),
|
||||
|
||||
TextInput::make('total_price')
|
||||
->prefix('$')
|
||||
->readOnly()
|
||||
->columnSpan(2),
|
||||
]),
|
||||
|
||||
Grid::make(9)
|
||||
->schema([
|
||||
TextInput::make('serviceFileCode')
|
||||
->label('Code')
|
||||
->columnSpan(1)
|
||||
->placeholder('A1234'),
|
||||
|
||||
Textarea::make('notes')
|
||||
->placeholder('Thread colors...')
|
||||
->columnSpan(8),
|
||||
]),
|
||||
])
|
||||
->reorderable()
|
||||
->cloneable()
|
||||
->columns(4)
|
||||
->columnSpan(2),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\IconColumn::make('alert')
|
||||
->getStateUsing(fn ($record) => $record->is_alert_danger || $record->is_alert_warning)
|
||||
->label('')
|
||||
->color(fn ($record) => $record->is_alert_danger ? 'danger' : 'warning')
|
||||
->icon(function ($record) {
|
||||
return $record->is_alert_danger
|
||||
? 'lucide-calendar-clock' : ($record->rush
|
||||
? OrderAttributes::rush->getIcon() : null);
|
||||
})
|
||||
->size(Tables\Columns\IconColumn\IconColumnSize::Medium),
|
||||
|
||||
TextColumn::make('internal_po')
|
||||
->label('Internal PO')
|
||||
->fontFamily('mono')
|
||||
->color('info')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('customer.company_name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('customer_po')
|
||||
->label('PO')
|
||||
->wrap()
|
||||
->weight('bold')
|
||||
->color('code')
|
||||
->searchable()
|
||||
->sortable()
|
||||
->extraHeaderAttributes([
|
||||
'class' => 'w-full',
|
||||
]),
|
||||
|
||||
TextColumn::make('order_date')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('due_date')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('status')
|
||||
->badge()
|
||||
->searchable()
|
||||
->sortable(),
|
||||
])
|
||||
|
||||
->defaultSort('order_date', 'desc')
|
||||
|
||||
->filters([
|
||||
Tables\Filters\Filter::make('order_date')
|
||||
->form([
|
||||
DatePicker::make('created_from'),
|
||||
DatePicker::make('created_until'),
|
||||
])
|
||||
->query(function (Builder $query, array $data): Builder {
|
||||
return $query
|
||||
->when(
|
||||
$data['created_from'],
|
||||
fn (Builder $query, $date): Builder => $query->whereDate('order_date', '>=', $date),
|
||||
)
|
||||
->when(
|
||||
$data['created_until'],
|
||||
fn (Builder $query, $date): Builder => $query->whereDate('order_date', '<=', $date),
|
||||
);
|
||||
}),
|
||||
], )
|
||||
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListOrders::route('/'),
|
||||
'create' => Pages\CreateOrder::route('/create'),
|
||||
'edit' => Pages\EditOrder::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\OrderResource\Pages;
|
||||
|
||||
use App\Enums\OrderAttributes;
|
||||
use App\Filament\Resources\OrderResource;
|
||||
use App\Models\Order;
|
||||
use App\Models\OrderProduct;
|
||||
use App\Models\ProductService;
|
||||
use App\Models\ProductSize;
|
||||
use App\Models\ServiceFile;
|
||||
use App\Models\ServiceType;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateOrder extends CreateRecord
|
||||
{
|
||||
protected static string $resource = OrderResource::class;
|
||||
|
||||
protected function handleRecordCreation(array $data): Order
|
||||
{
|
||||
// Attributes
|
||||
foreach (OrderAttributes::cases() as $case) {
|
||||
$data[$case->name] = false;
|
||||
}
|
||||
|
||||
$data['order_attributes'] = array_filter($data['order_attributes']);
|
||||
|
||||
foreach ($data['order_attributes'] as $attribute) {
|
||||
$data[OrderAttributes::from($attribute)->name] = true;
|
||||
}
|
||||
|
||||
unset($data['order_attributes']);
|
||||
|
||||
$order = Order::create($data);
|
||||
|
||||
// Create Order Products
|
||||
foreach ($data['order_products'] as $product) {
|
||||
$orderProduct = OrderProduct::create([
|
||||
'sku' => $product['sku'],
|
||||
'product_name' => $product['product_name'],
|
||||
'color' => $product['color'],
|
||||
'order_id' => $order->id,
|
||||
]);
|
||||
|
||||
$sizes = ['xs', 's', 'm', 'l', 'xl', '2xl', '3xl', 'osfa'];
|
||||
|
||||
foreach ($sizes as $size) {
|
||||
if ($product[$size] > 0) {
|
||||
ProductSize::create([
|
||||
'amount' => $product[$size],
|
||||
'size' => $size,
|
||||
'order_product_id' => $orderProduct->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ProductServices and ServiceFiles
|
||||
foreach ($data['services'] as $service) {
|
||||
$serviceFile = ServiceFile::create([
|
||||
'name' => strtoupper($service['serviceFileName']) ?? '',
|
||||
'code' => strtoupper($service['serviceFileCode']) ?? '',
|
||||
'width' => $service['serviceFileWidth'] ?? null,
|
||||
'height' => $service['serviceFileHeight'] ?? null,
|
||||
'setup_number' => $service['serviceFileSetupNumber'] ?? null,
|
||||
]);
|
||||
|
||||
ProductService::create([
|
||||
'service_type_id' => ServiceType::findOrFail($service['serviceType'])->id ?? null,
|
||||
'placement' => strtoupper($service['placement']) ?? null,
|
||||
'notes' => strtoupper($service['notes']) ?? null,
|
||||
'amount' => $service['amount'] ?? null,
|
||||
'amount_price' => $service['amount_price'] ?? null,
|
||||
'total_price' => $service['total_price'] ?? null,
|
||||
'service_file_id' => $serviceFile->id,
|
||||
'order_id' => $order->id,
|
||||
]);
|
||||
}
|
||||
|
||||
return $order;
|
||||
}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\OrderResource\Pages;
|
||||
|
||||
use App\Enums\OrderAttributes;
|
||||
use App\Filament\Resources\OrderResource;
|
||||
use App\Models\Order;
|
||||
use App\Models\OrderProduct;
|
||||
use App\Models\ProductService;
|
||||
use App\Models\ProductSize;
|
||||
use App\Models\ServiceFile;
|
||||
use App\Models\ServiceType;
|
||||
use Filament\Actions;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EditOrder extends EditRecord
|
||||
{
|
||||
protected static string $resource = OrderResource::class;
|
||||
|
||||
protected function mutateFormDataBeforeFill(array $data): array
|
||||
{
|
||||
$order = Order::findOrFail($data['id']);
|
||||
|
||||
// Order Products
|
||||
foreach ($order->orderProducts as $key => $product) {
|
||||
$data['order_products'][$key] = [
|
||||
'sku' => $product->sku,
|
||||
'product_name' => $product->product_name,
|
||||
'color' => $product->color,
|
||||
'xs' => $product->productSizes->where('size', 'xs')->first()->amount ?? null,
|
||||
's' => $product->productSizes->where('size', 's')->first()->amount ?? null,
|
||||
'm' => $product->productSizes->where('size', 'm')->first()->amount ?? null,
|
||||
'l' => $product->productSizes->where('size', 'l')->first()->amount ?? null,
|
||||
'xl' => $product->productSizes->where('size', 'xl')->first()->amount ?? null,
|
||||
'2xl' => $product->productSizes->where('size', '2xl')->first()->amount ?? null,
|
||||
'3xl' => $product->productSizes->where('size', '3xl')->first()->amount ?? null,
|
||||
'osfa' => $product->productSizes->where('size', 'osfa')->first()->amount ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
// Product Services
|
||||
foreach ($order->productServices as $key => $service) {
|
||||
$data['services'][$key] = [
|
||||
'placement' => $service->placement ?? '',
|
||||
'amount' => $service->amount ?? '',
|
||||
'amount_price' => $service->amount_price ?? '',
|
||||
'notes' => $service->notes ?? '',
|
||||
'serviceType' => $service->serviceType->id ?? '',
|
||||
'serviceFileName' => $service->serviceFile->name ?? '',
|
||||
'serviceFileWidth' => $service->serviceFile->width ?? '',
|
||||
'serviceFileHeight' => $service->serviceFile->height ?? '',
|
||||
'serviceFileCode' => $service->serviceFile->code ?? '',
|
||||
'serviceFileSetupNumber' => $service->serviceFile->setup_number ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
foreach (OrderAttributes::cases() as $case) {
|
||||
if ($data[$case->name]) {
|
||||
$data['order_attributes'][] = $case->value ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function handleRecordUpdate(Model $record, array $data): Model
|
||||
{
|
||||
// Correctly set attribute booleans
|
||||
foreach (OrderAttributes::cases() as $case) {
|
||||
$data[$case->name] = false;
|
||||
}
|
||||
|
||||
$data['order_attributes'] = array_filter($data['order_attributes']);
|
||||
|
||||
foreach ($data['order_attributes'] as $attribute) {
|
||||
$data[OrderAttributes::from($attribute)->name] = true;
|
||||
}
|
||||
|
||||
unset($data['order_attributes']);
|
||||
|
||||
$record->update($data);
|
||||
|
||||
// Delete old and create new Order Products
|
||||
foreach ($record->orderProducts as $product) {
|
||||
foreach ($product->productSizes as $size) {
|
||||
$size->delete();
|
||||
}
|
||||
|
||||
$product->delete();
|
||||
}
|
||||
|
||||
foreach ($data['order_products'] as $product) {
|
||||
$orderProduct = OrderProduct::create([
|
||||
'sku' => $product['sku'],
|
||||
'product_name' => $product['product_name'],
|
||||
'color' => $product['color'],
|
||||
'order_id' => $record->id,
|
||||
]);
|
||||
|
||||
$sizes = ['xs', 's', 'm', 'l', 'xl', '2xl', '3xl', 'osfa'];
|
||||
|
||||
foreach ($sizes as $size) {
|
||||
if ($product[$size] > 0) {
|
||||
ProductSize::create([
|
||||
'amount' => $product[$size],
|
||||
'size' => $size,
|
||||
'order_product_id' => $orderProduct->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete old and create new services
|
||||
foreach ($record->productServices as $service) {
|
||||
$service->delete();
|
||||
}
|
||||
|
||||
foreach ($data['services'] as $service) {
|
||||
$serviceFile = ServiceFile::create([
|
||||
'name' => strtoupper($service['serviceFileName']) ?? '',
|
||||
'code' => strtoupper($service['serviceFileCode']) ?? '',
|
||||
'width' => $service['serviceFileWidth'] ?? null,
|
||||
'height' => $service['serviceFileHeight'] ?? null,
|
||||
'setup_number' => $service['serviceFileSetupNumber'] ?? null,
|
||||
]);
|
||||
|
||||
ProductService::create([
|
||||
'service_type_id' => ServiceType::findOrFail($service['serviceType'])->id ?? null,
|
||||
'placement' => strtoupper($service['placement']) ?? null,
|
||||
'notes' => strtoupper($service['notes']) ?? null,
|
||||
'amount' => $service['amount'] ?? null,
|
||||
'amount_price' => $service['amount_price'] ?? null,
|
||||
'total_price' => $service['total_price'] ?? null,
|
||||
'service_file_id' => $serviceFile->id,
|
||||
'order_id' => $record->id,
|
||||
]);
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('save')
|
||||
->label('Save changes')
|
||||
->action('save')
|
||||
->icon('lucide-save'),
|
||||
Action::make('print')
|
||||
->icon('lucide-printer')
|
||||
->url(fn (Order $record) => route('orders.pdf', $record))
|
||||
->openUrlInNewTab(),
|
||||
Actions\DeleteAction::make()
|
||||
->icon('lucide-trash-2'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\OrderResource\Pages;
|
||||
|
||||
use App\Enums\OrderAttributes;
|
||||
use App\Enums\OrderStatus;
|
||||
use App\Filament\Resources\OrderResource;
|
||||
use App\Models\Order;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Components\Tab;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListOrders extends ListRecords
|
||||
{
|
||||
protected static string $resource = OrderResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getTabs(): array
|
||||
{
|
||||
return [
|
||||
'active' => Tab::make()
|
||||
->query(function ($query) {
|
||||
return $query
|
||||
->whereNot('status', OrderStatus::INVOICED)
|
||||
->whereNot('status', ORderStatus::SHIPPED);
|
||||
})
|
||||
->icon(OrderStatus::PRODUCTION->getIcon())
|
||||
->badge(function () {
|
||||
return Order::whereNot('status', OrderStatus::SHIPPED)
|
||||
->whereNot('status', OrderStatus::INVOICED)
|
||||
->count();
|
||||
}),
|
||||
'overdue' => Tab::make()
|
||||
->query(function ($query) {
|
||||
return $query->whereDate('due_date', '<=', today())
|
||||
->whereNot('status', OrderStatus::INVOICED)
|
||||
->whereNot('status', ORderStatus::SHIPPED);
|
||||
})
|
||||
->icon('lucide-calendar-clock')
|
||||
->badge(function () {
|
||||
$count = Order::whereDate('due_date', '<=', today())
|
||||
->whereNot('status', OrderStatus::INVOICED)
|
||||
->whereNot('status', ORderStatus::SHIPPED)
|
||||
->count();
|
||||
|
||||
return $count > 0 ? $count : null;
|
||||
})
|
||||
->badgeColor('danger'),
|
||||
|
||||
'rush' => Tab::make()
|
||||
->query(function ($query) {
|
||||
return $query->where('rush', true)
|
||||
->whereNot('status', OrderStatus::INVOICED)
|
||||
->whereNot('status', OrderStatus::SHIPPED);
|
||||
})
|
||||
->icon(OrderAttributes::rush->getIcon())
|
||||
->badge(function () {
|
||||
$count = Order::where('rush', true)
|
||||
->whereNot('status', OrderStatus::INVOICED)
|
||||
->whereNot('status', OrderStatus::SHIPPED)
|
||||
->count();
|
||||
|
||||
return $count > 0 ? $count : null;
|
||||
})
|
||||
->badgeColor('warning'),
|
||||
|
||||
null => Tab::make('All')
|
||||
->icon('lucide-layout-grid'),
|
||||
|
||||
// 'draft' => Tab::make()
|
||||
// ->query(fn ($query) => $query->where('status', OrderStatus::DRAFT->value))
|
||||
// ->icon(OrderStatus::DRAFT->getIcon()),
|
||||
//
|
||||
// 'approved' => Tab::make()
|
||||
// ->query(fn ($query) => $query->where('status', OrderStatus::APPROVED->value))
|
||||
// ->icon(OrderStatus::APPROVED->getIcon()),
|
||||
//
|
||||
// 'production' => Tab::make()
|
||||
// ->query(fn ($query) => $query->where('status', OrderStatus::PRODUCTION->value))
|
||||
// ->icon(OrderStatus::PRODUCTION->getIcon()),
|
||||
//
|
||||
// 'shipped' => Tab::make()
|
||||
// ->query(fn ($query) => $query->where('status', OrderStatus::SHIPPED->value))
|
||||
// ->icon(OrderStatus::SHIPPED->getIcon()),
|
||||
//
|
||||
// 'invoiced' => Tab::make()
|
||||
// ->query(fn ($query) => $query->where('status', OrderStatus::INVOICED->value))
|
||||
// ->icon(OrderStatus::INVOICED->getIcon()),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\OrderResource\Pages;
|
||||
|
||||
use App\Filament\Resources\OrderResource;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewOrder extends ViewRecord
|
||||
{
|
||||
protected static string $resource = OrderResource::class;
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\OrderResource\RelationManagers;
|
||||
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class OrderProductsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'orderProducts';
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('id')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
]);
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('id')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('sku'),
|
||||
Tables\Columns\TextColumn::make('product_name'),
|
||||
Tables\Columns\TextColumn::make('color'),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->headerActions([
|
||||
Tables\Actions\CreateAction::make(),
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
Tables\Actions\DeleteAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\PackingSlipResource\Pages;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Order;
|
||||
use App\Models\PackingSlip;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class PackingSlipResource extends Resource
|
||||
{
|
||||
protected static ?string $model = PackingSlip::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'lucide-package';
|
||||
|
||||
protected static ?string $navigationGroup = 'Management';
|
||||
|
||||
protected static ?int $navigationSort = 2;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
DatePicker::make('date_received'),
|
||||
TextInput::make('amount'),
|
||||
Select::make('customer_id')
|
||||
->options(Customer::all()->pluck('company_name', 'id'))
|
||||
->reactive()
|
||||
->searchable(),
|
||||
Select::make('order_id')
|
||||
->options(fn ($get): array => Order::where('customer_id', $get('customer_id') ?? null)
|
||||
->get()
|
||||
->pluck('customer_po', 'id')
|
||||
->toArray())
|
||||
->searchable(),
|
||||
TextArea::make('contents'),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('date_received')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
TextColumn::make('order.customer_po')
|
||||
->weight('bold')
|
||||
->color('code')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
TextColumn::make('contents'),
|
||||
TextColumn::make('amount'),
|
||||
TextColumn::make('order.customer.company_name')
|
||||
->sortable()
|
||||
->searchable(),
|
||||
])
|
||||
->defaultSort('date_received', 'desc')
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListPackingSlips::route('/'),
|
||||
'create' => Pages\CreatePackingSlip::route('/create'),
|
||||
'edit' => Pages\EditPackingSlip::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PackingSlipResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PackingSlipResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreatePackingSlip extends CreateRecord
|
||||
{
|
||||
protected static string $resource = PackingSlipResource::class;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PackingSlipResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PackingSlipResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPackingSlip extends EditRecord
|
||||
{
|
||||
protected static string $resource = PackingSlipResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PackingSlipResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PackingSlipResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListPackingSlips extends ListRecords
|
||||
{
|
||||
protected static string $resource = PackingSlipResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\QuoteResource\Pages;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Order;
|
||||
use App\Models\Quote;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Split;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class QuoteResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Quote::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'lucide-quote';
|
||||
|
||||
protected static ?string $navigationGroup = 'Production';
|
||||
|
||||
protected static ?int $navigationSort = 1;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Section::make([
|
||||
Split::make([
|
||||
Select::make('customer_id')
|
||||
->required()
|
||||
->label('Customer')
|
||||
->options(Customer::all()->pluck('company_name', 'id'))
|
||||
->reactive()
|
||||
->searchable(),
|
||||
|
||||
Select::make('order_id')
|
||||
->label('Order')
|
||||
->options(fn ($get): array => Order::where('customer_id', $get('customer_id') ?? null)
|
||||
->get()
|
||||
->pluck('customer_po', 'id')
|
||||
->toArray())
|
||||
->searchable(),
|
||||
|
||||
])->columnSpan(2),
|
||||
Textarea::make('body')
|
||||
->columnSpan(2)
|
||||
->rows(8),
|
||||
]),
|
||||
])->columns(3);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('order.customer.company_name')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('order.customer_po')
|
||||
->searchable()
|
||||
->weight('bold')
|
||||
->color('code'),
|
||||
Tables\Columns\TextColumn::make('body')
|
||||
->searchable()
|
||||
->limit(100),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->date('Y-m-d')
|
||||
->sortable(),
|
||||
])
|
||||
->defaultSort('created_at', 'desc')
|
||||
->groups([
|
||||
'order.customer.company_name',
|
||||
])
|
||||
->filters([
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListQuotes::route('/'),
|
||||
'create' => Pages\CreateQuote::route('/create'),
|
||||
'edit' => Pages\EditQuote::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\QuoteResource\Pages;
|
||||
|
||||
use App\Filament\Resources\QuoteResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateQuote extends CreateRecord
|
||||
{
|
||||
protected static string $resource = QuoteResource::class;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\QuoteResource\Pages;
|
||||
|
||||
use App\Filament\Resources\QuoteResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditQuote extends EditRecord
|
||||
{
|
||||
protected static string $resource = QuoteResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\QuoteResource\Pages;
|
||||
|
||||
use App\Filament\Resources\QuoteResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListQuotes extends ListRecords
|
||||
{
|
||||
protected static string $resource = QuoteResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\ServiceTypeResource\Pages;
|
||||
use App\Filament\Resources\ServiceTypeResource\Widgets\ServiceTypeOverview;
|
||||
use App\Models\ServiceType;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ServiceTypeResource extends Resource
|
||||
{
|
||||
protected static ?string $model = ServiceType::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
|
||||
|
||||
protected static ?string $navigationGroup = 'Reports';
|
||||
|
||||
protected static ?string $label = 'Product Services';
|
||||
|
||||
public static function getWidgets(): array
|
||||
{
|
||||
return [
|
||||
ServiceTypeOverview::class,
|
||||
];
|
||||
}
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->label('Code'),
|
||||
|
||||
Tables\Columns\TextColumn::make('value')
|
||||
->label('Long Name')
|
||||
->extraHeaderAttributes(['class' => 'w-full']),
|
||||
|
||||
Tables\Columns\TextColumn::make('quantity')
|
||||
->alignRight()
|
||||
->getStateUsing(function (Table $table, Model $record) {
|
||||
return $record->getQuantityAttribute(
|
||||
$table->getFilter('created_at')->getState()['created_at'],
|
||||
$table->getFilter('created_until')->getState()['created_until']
|
||||
);
|
||||
}),
|
||||
|
||||
Tables\Columns\TextColumn::make('amount')
|
||||
->alignRight()
|
||||
->getStateUsing(function (Table $table, Model $record) {
|
||||
return $record->getAmountAttribute(
|
||||
$table->getFilter('created_at')->getState()['created_at'],
|
||||
$table->getFilter('created_until')->getState()['created_until']
|
||||
);
|
||||
})
|
||||
->money('usd'),
|
||||
|
||||
Tables\Columns\TextColumn::make('salesPercentage')
|
||||
->alignRight()
|
||||
->getStateUsing(function (Table $table, Model $record) {
|
||||
return $record->getSalesPercentageAttribute(
|
||||
$table->getFilter('created_at')->getState()['created_at'],
|
||||
$table->getFilter('created_until')->getState()['created_until']
|
||||
);
|
||||
})
|
||||
->suffix('%')
|
||||
->label('% sales'),
|
||||
|
||||
Tables\Columns\TextColumn::make('averagePrice')
|
||||
->getStateUsing(function (Table $table, Model $record) {
|
||||
return $record->getAveragePriceAttribute(
|
||||
$table->getFilter('created_at')->getState()['created_at'],
|
||||
$table->getFilter('created_until')->getState()['created_until']
|
||||
);
|
||||
})
|
||||
->alignRight()
|
||||
->label('Average')
|
||||
->prefix('$'),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\Filter::make('created_at')
|
||||
->form([
|
||||
DatePicker::make('created_at')
|
||||
->label('From date'),
|
||||
]),
|
||||
|
||||
Tables\Filters\Filter::make('created_until')
|
||||
->form([
|
||||
DatePicker::make('created_until')
|
||||
->label('Until date'),
|
||||
]),
|
||||
], layout: Tables\Enums\FiltersLayout::AboveContentCollapsible)
|
||||
->actions([
|
||||
])
|
||||
|
||||
->bulkActions([
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListServiceTypes::route('/'),
|
||||
'create' => Pages\CreateServiceType::route('/create'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\ServiceTypeResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ServiceTypeResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateServiceType extends CreateRecord
|
||||
{
|
||||
protected static string $resource = ServiceTypeResource::class;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\ServiceTypeResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ServiceTypeResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditServiceType extends EditRecord
|
||||
{
|
||||
protected static string $resource = ServiceTypeResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\ServiceTypeResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ServiceTypeResource;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListServiceTypes extends ListRecords
|
||||
{
|
||||
protected static string $resource = ServiceTypeResource::class;
|
||||
|
||||
protected function getHeaderWidgets(): array
|
||||
{
|
||||
return [
|
||||
// ServiceTypeResource\Widgets\ServiceTypeOverview::class,
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
// Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\ServiceTypeResource\Widgets;
|
||||
|
||||
use Filament\Widgets\ChartWidget;
|
||||
|
||||
class ServiceTypeOverview extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'Services';
|
||||
|
||||
protected static ?string $maxHeight = '200px';
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
return [
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Test Label',
|
||||
'data' => [30, 15, 25, 30],
|
||||
],
|
||||
],
|
||||
'labels' => [
|
||||
'Test 1',
|
||||
'Test 2',
|
||||
'Test 3',
|
||||
'Test 4',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'pie';
|
||||
}
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Enums\ShippingType;
|
||||
use App\Filament\Resources\ShippingEntryResource\Pages;
|
||||
use App\Models\ShippingEntry;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Split;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Support\Enums\IconPosition;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Grouping\Group;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ShippingEntryResource extends Resource
|
||||
{
|
||||
protected static ?string $model = ShippingEntry::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'lucide-truck';
|
||||
|
||||
protected static ?string $navigationGroup = 'Management';
|
||||
|
||||
protected static ?int $navigationSort = 3;
|
||||
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Section::make([
|
||||
Fieldset::make('Primary information')
|
||||
->schema([
|
||||
Select::make('customer')
|
||||
->relationship('customer', 'company_name')
|
||||
->searchable()
|
||||
->required(),
|
||||
|
||||
ToggleButtons::make('shipping_type')
|
||||
->options(ShippingType::class)
|
||||
->inline()
|
||||
->required(),
|
||||
|
||||
TextInput::make('courier')
|
||||
->placeholder('UPS, Purolator...'),
|
||||
]),
|
||||
|
||||
Split::make([
|
||||
Fieldset::make('Account Details')
|
||||
->schema([
|
||||
TextInput::make('account_title')
|
||||
->label('Title')
|
||||
->prefixIcon('lucide-folder-pen')
|
||||
->placeholder('What is this account used for?')
|
||||
->columnSpan(2),
|
||||
TextInput::make('account_url')
|
||||
->label('URL')
|
||||
->prefixIcon('lucide-globe')
|
||||
->placeholder('Shipping website')
|
||||
->url()
|
||||
->columnSpan(2),
|
||||
TextInput::make('account_username')
|
||||
->label('Username')
|
||||
->prefixIcon('lucide-circle-user')
|
||||
->placeholder('...'),
|
||||
TextInput::make('account_password')
|
||||
->label('Password')
|
||||
->prefixIcon('lucide-key-round')
|
||||
->placeholder('...'),
|
||||
])->columnSpan(1),
|
||||
|
||||
Fieldset::make('Shipping Instructions')
|
||||
->schema([
|
||||
TextInput::make('info_needed')
|
||||
->label('Instructions')
|
||||
->prefixIcon('lucide-pencil')
|
||||
->placeholder('Example: put PO on box')
|
||||
->columnSpan(2),
|
||||
TextInput::make('notify')
|
||||
->placeholder('Who to email and CC?')
|
||||
->prefixIcon('lucide-users-round')
|
||||
->columnSpan(2),
|
||||
TextArea::make('notes')
|
||||
->placeholder('Any additional information...')
|
||||
->rows(2)
|
||||
->columnSpan(2),
|
||||
]),
|
||||
])->columnSpan(2),
|
||||
])->columns(2),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('shipping_type')
|
||||
->label('Type')
|
||||
->sortable(),
|
||||
TextColumn::make('courier')
|
||||
->url(fn ($record) => $record->account_url ?? null, shouldOpenInNewTab: true)
|
||||
->icon(fn ($record) => $record->account_url ? 'lucide-external-link' : null)
|
||||
->iconPosition(IconPosition::After)
|
||||
->searchable(query: function (Builder $query, $search) {
|
||||
return $query
|
||||
->where('courier', 'like', "%{$search}%")
|
||||
->orWhereHas('customer', function (Builder $query) use ($search) {
|
||||
return $query->where('company_name', 'like', "%{$search}%");
|
||||
});
|
||||
}),
|
||||
|
||||
TextColumn::make('account_title'),
|
||||
TextColumn::make('info_needed'),
|
||||
TextColumn::make('notify'),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
// Tables\Actions\BulkActionGroup::make([
|
||||
// Tables\Actions\DeleteBulkAction::make(),
|
||||
// ]),
|
||||
])
|
||||
->defaultGroup(
|
||||
Group::make('customer.company_name')
|
||||
->titlePrefixedWithLabel(false)
|
||||
);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListShippingEntries::route('/'),
|
||||
'create' => Pages\CreateShippingEntry::route('/create'),
|
||||
'edit' => Pages\EditShippingEntry::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\ShippingEntryResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ShippingEntryResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateShippingEntry extends CreateRecord
|
||||
{
|
||||
protected static string $resource = ShippingEntryResource::class;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\ShippingEntryResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ShippingEntryResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditShippingEntry extends EditRecord
|
||||
{
|
||||
protected static string $resource = ShippingEntryResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\ShippingEntryResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ShippingEntryResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListShippingEntries extends ListRecords
|
||||
{
|
||||
protected static string $resource = ShippingEntryResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Enums\OrderStatus;
|
||||
use App\Models\Order;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as BaseWidget;
|
||||
|
||||
class ActiveOrdersTable extends BaseWidget
|
||||
{
|
||||
protected static ?int $sort = 2;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(
|
||||
Order::query()
|
||||
->where('status', '!=', OrderStatus::SHIPPED)
|
||||
->where('status', '!=', OrderStatus::INVOICED)
|
||||
)
|
||||
->columns([
|
||||
TextColumn::make('customer.company_name'),
|
||||
TextColumn::make('customer_po')
|
||||
->color('code')
|
||||
->weight('bold'),
|
||||
TextColumn::make('status')
|
||||
// ->color(OrderStatus::class)
|
||||
->badge(),
|
||||
])
|
||||
->defaultPaginationPageOption(5);
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Enums\OrderStatus;
|
||||
use App\Models\Order;
|
||||
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
|
||||
use Filament\Widgets\StatsOverviewWidget\Stat;
|
||||
|
||||
class OrderStats extends BaseWidget
|
||||
{
|
||||
// protected int|string|array $columnSpan = '2';
|
||||
|
||||
protected function getStats(): array
|
||||
{
|
||||
return [
|
||||
Stat::make('This Month', $this->getOrdersPast30Days())
|
||||
->icon('heroicon-s-calendar')
|
||||
->chartColor('success')
|
||||
->chart($this->getOrdersInPast30DaysChart())
|
||||
->description('New orders in the past 30 days'),
|
||||
|
||||
Stat::make('Active Orders', $this->getActiveOrders())
|
||||
->icon('heroicon-o-arrow-path')
|
||||
->description('Orders that have yet to be completed'),
|
||||
|
||||
Stat::make('Due Today', $this->getDueOrders())
|
||||
->icon('heroicon-o-clock')
|
||||
->chartColor('info')
|
||||
->chart($this->getDueOrdersChart())
|
||||
->description('Orders that are scheduled to be due today'),
|
||||
];
|
||||
}
|
||||
|
||||
private function getActiveOrders(): string
|
||||
{
|
||||
return Order::all()
|
||||
->where('order_status', '!=', OrderStatus::SHIPPED)
|
||||
->where('order_status', '!=', OrderStatus::INVOICED)
|
||||
->count();
|
||||
}
|
||||
|
||||
private function getOrdersPast30Days(): string
|
||||
{
|
||||
return Order::all()
|
||||
->where('order_status', '!=', OrderStatus::SHIPPED)
|
||||
->where('order_status', '!=', OrderStatus::INVOICED)
|
||||
->whereBetween('created_at', [now()->startOfMonth(), now()->endOfMonth()])
|
||||
->count();
|
||||
}
|
||||
|
||||
private function getOrdersInPast30DaysChart(): array
|
||||
{
|
||||
$chart = [];
|
||||
$points = 30;
|
||||
$startDate = today()->subDays(31);
|
||||
|
||||
for ($i = 0; $i < $points; $i++) {
|
||||
$chart[$i] = Order::where('order_date', $startDate->addDay())->count();
|
||||
}
|
||||
|
||||
return $chart;
|
||||
}
|
||||
|
||||
private function getDueOrders(): string
|
||||
{
|
||||
return Order::all()
|
||||
->where('order_status', '!=', OrderStatus::SHIPPED)
|
||||
->where('order_status', '!=', OrderStatus::INVOICED)
|
||||
->where('due_date', '<=', now())
|
||||
->count();
|
||||
}
|
||||
|
||||
private function getDueOrdersChart(): array
|
||||
{
|
||||
$chart = [];
|
||||
$points = 30;
|
||||
$startDate = today()->subDays(31);
|
||||
|
||||
for ($i = 0; $i < $points; $i++) {
|
||||
$chart[$i] = Order::where('due_date', $startDate->addDay())->count();
|
||||
}
|
||||
|
||||
return $chart;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Enums\OrderStatus;
|
||||
use App\Models\Order;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as BaseWidget;
|
||||
|
||||
class RushOrdersTable extends BaseWidget
|
||||
{
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(
|
||||
Order::query()
|
||||
->where('status', '!=', OrderStatus::SHIPPED)
|
||||
->where('status', '!=', OrderStatus::INVOICED)
|
||||
->where('rush', true)
|
||||
->orderByDesc('due_date')
|
||||
)
|
||||
->columns([
|
||||
TextColumn::make('customer.company_name'),
|
||||
TextColumn::make('customer_po')
|
||||
->color('code')
|
||||
->weight('bold'),
|
||||
TextColumn::make('status')
|
||||
->badge(),
|
||||
])
|
||||
->defaultPaginationPageOption(5);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Invoice;
|
||||
use Spatie\Browsershot\Browsershot;
|
||||
use Spatie\LaravelPdf\Facades\Pdf;
|
||||
|
||||
class InvoiceController extends Controller
|
||||
{
|
||||
public function pdf(int $id)
|
||||
{
|
||||
$invoice = Invoice::find($id);
|
||||
$url = strtolower('invoice-'.$invoice->internal_id.'.pdf');
|
||||
|
||||
Pdf::view('pdf.invoice', ['invoice' => $invoice])
|
||||
->withBrowsershot(function (Browsershot $browsershot) {
|
||||
$browsershot->noSandbox();
|
||||
})
|
||||
->margins(8, 8, 15, 8)
|
||||
->footerView('pdf.invoice-footer', ['invoice' => $invoice])
|
||||
->save($url);
|
||||
|
||||
return redirect($url);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
class QuoteController extends Controller
|
||||
{
|
||||
//
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\InvoiceStatus;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
|
||||
class Invoice extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'customer_id',
|
||||
'internal_id',
|
||||
'status',
|
||||
'subtotal',
|
||||
'total',
|
||||
'gst',
|
||||
'pst',
|
||||
'date',
|
||||
];
|
||||
|
||||
protected $appends = [
|
||||
'gst_amount',
|
||||
'pst_amount',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'date' => 'datetime',
|
||||
'total' => 'decimal:2',
|
||||
'subtotal' => 'decimal:2',
|
||||
'status' => InvoiceStatus::class,
|
||||
];
|
||||
|
||||
public static function boot(): void
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::created(function ($model) {
|
||||
$model->attributes['internal_id'] = $model->generateInternalId($model->id);
|
||||
$model->save();
|
||||
});
|
||||
}
|
||||
|
||||
public function setStatus(InvoiceStatus $status)
|
||||
{
|
||||
if ($this->status !== $status) {
|
||||
$this->status = $status;
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function generateInternalId(int $id): string
|
||||
{
|
||||
$po = str_pad(strval($id), 4, '0', STR_PAD_LEFT);
|
||||
$year = date('y');
|
||||
|
||||
return 'TN-IN-'.$year.'-'.$po;
|
||||
}
|
||||
|
||||
public function calculateTotals(): void
|
||||
{
|
||||
$subtotal = 0;
|
||||
|
||||
foreach ($this->orders as $order) {
|
||||
$subtotal += $order->total_service_price;
|
||||
}
|
||||
|
||||
$this->subtotal = $subtotal;
|
||||
$this->total = $subtotal + $this->gst_amount + $this->pst_amount;
|
||||
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function getGstAmountAttribute(): float
|
||||
{
|
||||
if ($this->gst) {
|
||||
return number_format($this->subtotal * 0.05, 2);
|
||||
}
|
||||
|
||||
return 0.00;
|
||||
}
|
||||
|
||||
public function getPstAmountAttribute(): float
|
||||
{
|
||||
if ($this->pst) {
|
||||
return number_format($this->subtotal * 0.07, 2);
|
||||
}
|
||||
|
||||
return 0.00;
|
||||
}
|
||||
|
||||
public function orders(): HasMany
|
||||
{
|
||||
return $this->HasMany(Order::class);
|
||||
}
|
||||
|
||||
public function customer(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Customer::class);
|
||||
}
|
||||
|
||||
public function productServices(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(ProductService::class, Order::class);
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Quote extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'body',
|
||||
'order_id',
|
||||
];
|
||||
|
||||
public function order(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Order::class);
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class ServiceType extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'value',
|
||||
];
|
||||
|
||||
protected $appends = [
|
||||
'quantity',
|
||||
'amount',
|
||||
'sales_percentage',
|
||||
'average_price',
|
||||
];
|
||||
|
||||
public function getQuantityAttribute($created_at = null, $created_until = null): int
|
||||
{
|
||||
return $this->productServices()
|
||||
->when($created_at, fn ($query) => $query->whereDate('created_at', '>=', $created_at))
|
||||
->when($created_until, fn ($query) => $query->whereDate('created_at', '<=', $created_until))
|
||||
->sum('amount');
|
||||
}
|
||||
|
||||
public function getAmountAttribute($created_at = null, $created_until = null): float
|
||||
{
|
||||
return $this->productServices()
|
||||
->when($created_at, fn ($query) => $query->whereDate('created_at', '>=', $created_at))
|
||||
->when($created_until, fn ($query) => $query->whereDate('created_at', '<=', $created_until))
|
||||
->sum('amount_price');
|
||||
}
|
||||
|
||||
public function getSalesPercentageAttribute($created_at = null, $created_until = null): float
|
||||
{
|
||||
$query = ProductService::query()
|
||||
->when($created_at, fn ($query) => $query->whereDate('created_at', '>=', $created_at))
|
||||
->when($created_until, fn ($query) => $query->whereDate('created_until', '<=', $created_until));
|
||||
|
||||
$total = $query->count();
|
||||
|
||||
$part = $query->where('service_type_id', $this->id)->count();
|
||||
|
||||
if ($total == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return round(($part / $total) * 100, 1);
|
||||
}
|
||||
|
||||
public function getAveragePriceAttribute($created_at = null, $created_until = null): string
|
||||
{
|
||||
$quantity = $this->getQuantityAttribute($created_at, $created_until);
|
||||
$amount = $this->getAmountAttribute($created_at, $created_until);
|
||||
|
||||
if ($quantity == 0) {
|
||||
return '0.0';
|
||||
}
|
||||
|
||||
try {
|
||||
return number_format($quantity / $amount, 2);
|
||||
} catch (\Exception $exception) {
|
||||
dd($quantity, $amount, $exception);
|
||||
}
|
||||
}
|
||||
|
||||
public function productServices(): HasMany
|
||||
{
|
||||
return $this->hasMany(ProductService::class);
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers\Filament;
|
||||
|
||||
use Filament\Http\Middleware\Authenticate;
|
||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||
use Filament\Pages;
|
||||
use Filament\Panel;
|
||||
use Filament\PanelProvider;
|
||||
use Filament\Support\Colors\Color;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
|
||||
class AdminPanelProvider extends PanelProvider
|
||||
{
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
->default()
|
||||
->id('admin')
|
||||
->path('admin')
|
||||
->login()
|
||||
->colors([
|
||||
'primary' => Color::Blue,
|
||||
'code' => Color::hex('#d63384'),
|
||||
'invoiced' => Color::hex('#900090'),
|
||||
])
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
|
||||
->pages([
|
||||
Pages\Dashboard::class,
|
||||
])
|
||||
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
|
||||
->widgets([])
|
||||
->middleware([
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartSession::class,
|
||||
AuthenticateSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
DisableBladeIconComponents::class,
|
||||
DispatchServingFilamentEvent::class,
|
||||
])
|
||||
->authMiddleware([
|
||||
Authenticate::class,
|
||||
])
|
||||
->sidebarWidth('13rem');
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Broadcasting
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| By uncommenting the Laravel Echo configuration, you may connect Filament
|
||||
| to any Pusher-compatible websockets server.
|
||||
|
|
||||
| This will allow your users to receive real-time notifications.
|
||||
|
|
||||
*/
|
||||
|
||||
'broadcasting' => [
|
||||
|
||||
// 'echo' => [
|
||||
// 'broadcaster' => 'pusher',
|
||||
// 'key' => env('VITE_PUSHER_APP_KEY'),
|
||||
// 'cluster' => env('VITE_PUSHER_APP_CLUSTER'),
|
||||
// 'wsHost' => env('VITE_PUSHER_HOST'),
|
||||
// 'wsPort' => env('VITE_PUSHER_PORT'),
|
||||
// 'wssPort' => env('VITE_PUSHER_PORT'),
|
||||
// 'authEndpoint' => '/broadcasting/auth',
|
||||
// 'disableStats' => true,
|
||||
// 'encrypted' => true,
|
||||
// 'forceTLS' => true,
|
||||
// ],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Filesystem Disk
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the storage disk Filament will use to store files. You may use
|
||||
| any of the disks defined in the `config/filesystems.php`.
|
||||
|
|
||||
*/
|
||||
|
||||
'default_filesystem_disk' => env('FILAMENT_FILESYSTEM_DISK', 'public'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Assets Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the directory where Filament's assets will be published to. It
|
||||
| is relative to the `public` directory of your Laravel application.
|
||||
|
|
||||
| After changing the path, you should run `php artisan filament:assets`.
|
||||
|
|
||||
*/
|
||||
|
||||
'assets_path' => null,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Path
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is the directory that Filament will use to store cache files that
|
||||
| are used to optimize the registration of components.
|
||||
|
|
||||
| After changing the path, you should run `php artisan filament:cache-components`.
|
||||
|
|
||||
*/
|
||||
|
||||
'cache_path' => base_path('bootstrap/cache/filament'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Livewire Loading Delay
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This sets the delay before loading indicators appear.
|
||||
|
|
||||
| Setting this to 'none' makes indicators appear immediately, which can be
|
||||
| desirable for high-latency connections. Setting it to 'default' applies
|
||||
| Livewire's standard 200ms delay.
|
||||
|
|
||||
*/
|
||||
|
||||
'livewire_loading_delay' => 'default',
|
||||
|
||||
];
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue