diff --git a/app/Filament/Resources/InvoiceReportResource.php b/app/Filament/Resources/InvoiceReportResource.php index e53aa51..e171017 100644 --- a/app/Filament/Resources/InvoiceReportResource.php +++ b/app/Filament/Resources/InvoiceReportResource.php @@ -11,6 +11,7 @@ use Filament\Forms\Form; use Filament\Resources\Resource; use Filament\Support\Enums\FontFamily; +use Filament\Support\Enums\FontWeight; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; @@ -72,6 +73,7 @@ public static function table(Table $table): Table ->label('End Date') ->date('Y-m-d'), TextColumn::make('total') + ->weight(FontWeight::Bold) ->money(), ]) ->defaultSort('created_at', 'desc') @@ -103,8 +105,8 @@ public static function getPages(): array { return [ 'index' => Pages\ListInvoiceReports::route('/'), - 'edit' => Pages\EditInvoiceReport::route('/{record}/edit'), 'create' => Pages\CreateInvoiceReport::route('/create'), + 'view' => Pages\ViewInvoiceReport::route('/{record}'), ]; } } diff --git a/app/Filament/Resources/InvoiceReportResource/Pages/ViewInvoiceReport.php b/app/Filament/Resources/InvoiceReportResource/Pages/ViewInvoiceReport.php index 7a28647..7d621bc 100644 --- a/app/Filament/Resources/InvoiceReportResource/Pages/ViewInvoiceReport.php +++ b/app/Filament/Resources/InvoiceReportResource/Pages/ViewInvoiceReport.php @@ -3,6 +3,8 @@ namespace App\Filament\Resources\InvoiceReportResource\Pages; use App\Filament\Resources\InvoiceReportResource; +use App\Models\InvoiceReport; +use Filament\Actions\Action; use Filament\Resources\Pages\ViewRecord; class ViewInvoiceReport extends ViewRecord @@ -14,6 +16,10 @@ class ViewInvoiceReport extends ViewRecord protected function getHeaderActions(): array { return [ + Action::make('print') + ->icon('lucide-printer') + ->url(fn (InvoiceReport $record) => route('pdf.invoice-report', $record)) + ->openUrlInNewTab(), ]; } } diff --git a/app/Filament/Resources/InvoiceReportResource/RelationManagers/InvoicesRelationManager.php b/app/Filament/Resources/InvoiceReportResource/RelationManagers/InvoicesRelationManager.php index 993f67a..4e6c170 100644 --- a/app/Filament/Resources/InvoiceReportResource/RelationManagers/InvoicesRelationManager.php +++ b/app/Filament/Resources/InvoiceReportResource/RelationManagers/InvoicesRelationManager.php @@ -2,10 +2,12 @@ namespace App\Filament\Resources\InvoiceReportResource\RelationManagers; +use App\Filament\Resources\InvoiceResource; use Filament\Forms; use Filament\Forms\Form; use Filament\Resources\RelationManagers\RelationManager; -use Filament\Tables; +use Filament\Support\Enums\FontWeight; +use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; class InvoicesRelationManager extends RelationManager @@ -26,25 +28,40 @@ public function table(Table $table): Table { return $table ->recordTitleAttribute('internal_id') + ->recordUrl(fn ($record) => InvoiceResource::getUrl('edit', ['record' => $record->id])) ->columns([ - Tables\Columns\TextColumn::make('internal_id'), + TextColumn::make('internal_id') + ->label('ID') + ->extraHeaderAttributes(['class' => 'w-full']) + ->color('primary'), + TextColumn::make('date') + ->label('Created') + ->date(), + TextColumn::make('subtotal') + ->alignRight() + ->money(), + TextColumn::make('gst_amount') + ->alignRight() + ->money(), + TextColumn::make('pst_amount') + ->alignRight() + ->formatStateUsing(function ($state) { + return $state == 0.00 ? '-' : '$'.$state; + }), + TextColumn::make('total') + ->alignRight() + ->money() + ->weight(FontWeight::Bold), + TextColumn::make('status'), ]) ->filters([ // ]) ->headerActions([ - // Tables\Actions\CreateAction::make(), - Tables\Actions\AssociateAction::make(), ]) ->actions([ - Tables\Actions\DissociateAction::make(), - // Tables\Actions\EditAction::make(), - // Tables\Actions\DeleteAction::make(), ]) ->bulkActions([ - // Tables\Actions\BulkActionGroup::make([ - // Tables\Actions\DeleteBulkAction::make(), - // ]), ]); } } diff --git a/app/Filament/Resources/InvoiceReportResource/RelationManagers/OrdersRelationManager.php b/app/Filament/Resources/InvoiceReportResource/RelationManagers/OrdersRelationManager.php deleted file mode 100644 index b3abd8e..0000000 --- a/app/Filament/Resources/InvoiceReportResource/RelationManagers/OrdersRelationManager.php +++ /dev/null @@ -1,48 +0,0 @@ -schema([ - Forms\Components\TextInput::make('customer_po') - ->required() - ->maxLength(255), - ]); - } - - public function table(Table $table): Table - { - return $table - ->recordTitleAttribute('customer_po') - ->columns([ - Tables\Columns\TextColumn::make('customer_po'), - ]) - ->filters([ - // - ]) - ->headerActions([ - Tables\Actions\CreateAction::make(), - ]) - ->actions([ - Tables\Actions\EditAction::make(), - Tables\Actions\DeleteAction::make(), - ]) - ->bulkActions([ - Tables\Actions\BulkActionGroup::make([ - Tables\Actions\DeleteBulkAction::make(), - ]), - ]); - } -} diff --git a/app/Filament/Resources/InvoiceResource/RelationManagers/OrdersRelationManager.php b/app/Filament/Resources/InvoiceResource/RelationManagers/OrdersRelationManager.php index d36d1fc..2410e6a 100644 --- a/app/Filament/Resources/InvoiceResource/RelationManagers/OrdersRelationManager.php +++ b/app/Filament/Resources/InvoiceResource/RelationManagers/OrdersRelationManager.php @@ -2,6 +2,7 @@ namespace App\Filament\Resources\InvoiceResource\RelationManagers; +use App\Filament\Resources\OrderResource; use Filament\Forms; use Filament\Forms\Form; use Filament\Resources\RelationManagers\RelationManager; @@ -27,6 +28,7 @@ public function table(Table $table): Table { return $table ->recordTitleAttribute('customer_po') + ->recordUrl(fn ($record) => OrderResource::getUrl('edit', ['record' => $record->id])) ->columns([ Tables\Columns\TextColumn::make('customer_po') ->color('code') diff --git a/app/Filament/Resources/OrderResource.php b/app/Filament/Resources/OrderResource.php index 4d6192c..56fdc18 100644 --- a/app/Filament/Resources/OrderResource.php +++ b/app/Filament/Resources/OrderResource.php @@ -22,13 +22,16 @@ use Filament\Forms\Form; use Filament\Forms\Get; use Filament\Forms\Set; +use Filament\Notifications\Notification; use Filament\Resources\Resource; +use Filament\Support\Enums\MaxWidth; 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; +use Illuminate\Database\Eloquent\Collection; class OrderResource extends Resource { @@ -151,10 +154,22 @@ public static function form(Form $form): Form Grid::make(19) ->schema([ Select::make('serviceType') - ->options(ServiceType::all()->pluck('name', 'id')) - ->columnSpan(2) + ->options(ServiceType::all()->pluck('value', 'id')) + ->columnSpan(4) ->placeholder('Select...') - ->searchable(), + ->searchable() + ->createOptionForm([ + TextInput::make('name') + ->label('Code') + ->placeholder('Abbreviation here (example: \'Emb\'') + ->required(), + TextInput::make('value') + ->placeholder('Full name here (example: \'Embroidery\'') + ->required(), + ]) + ->createOptionUsing(function (array $data): int { + return ServiceType::create($data)->getKey(); + }), TextInput::make('placement') ->columnSpan(3), TextInput::make('serviceFileName') @@ -176,31 +191,32 @@ public static function form(Form $form): Form 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)); - }) +// ->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') + ->label('Amount') ->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)); - }) +// ->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), + // TextInput::make('total_price') + // ->prefix('$') + // ->readOnly() + // ->columnSpan(2), ]), Grid::make(9) @@ -299,6 +315,28 @@ public static function table(Table $table): Table ]) ->bulkActions([ + Tables\Actions\BulkAction::make('updateStatus') + ->form([ + Select::make('status') + ->options(OrderStatus::class), + ]) + ->modalHeading('Change selected orders status') + ->modalWidth(MaxWidth::Medium) + ->action(function (array $data, Collection $records): void { + foreach ($records as $record) { + $record->status = $data['status']; + $record->save(); + } + + Notification::make() + ->title(count($records).' item(s) updated successfully') + ->success() + ->send(); + }) + ->icon('lucide-pen') + ->color('info') + ->deselectRecordsAfterCompletion(), + Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make(), ]), diff --git a/app/Filament/Resources/OrderResource/Pages/ListOrders.php b/app/Filament/Resources/OrderResource/Pages/ListOrders.php index a22cb38..c59a6d7 100644 --- a/app/Filament/Resources/OrderResource/Pages/ListOrders.php +++ b/app/Filament/Resources/OrderResource/Pages/ListOrders.php @@ -9,6 +9,7 @@ use Filament\Actions; use Filament\Resources\Components\Tab; use Filament\Resources\Pages\ListRecords; +use Illuminate\Database\Eloquent\Builder; class ListOrders extends ListRecords { @@ -21,6 +22,8 @@ protected function getHeaderActions(): array ]; } + // protected function applyColumnSearchesToTableQuery(Builder $query): Builder {} + public function getTabs(): array { return [ @@ -36,6 +39,19 @@ public function getTabs(): array ->whereNot('status', OrderStatus::INVOICED) ->count(); }), + + 'unprinted' => Tab::make() + ->query(function ($query) { + return $query->where('printed', false); + }) + ->icon('lucide-printer') + ->badge(function () { + $count = Order::where('printed', false)->count(); + + return $count > 0 ? $count : null; + }) + ->badgeColor('success'), + 'overdue' => Tab::make() ->query(function ($query) { return $query->whereDate('due_date', '<=', today()) @@ -72,26 +88,6 @@ public function getTabs(): array 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()), ]; } } diff --git a/app/Filament/Resources/PackingSlipResource.php b/app/Filament/Resources/PackingSlipResource.php index e93148b..ecd9267 100644 --- a/app/Filament/Resources/PackingSlipResource.php +++ b/app/Filament/Resources/PackingSlipResource.php @@ -31,7 +31,8 @@ public static function form(Form $form): Form return $form ->schema([ DatePicker::make('date_received'), - TextInput::make('amount'), + TextInput::make('amount') + ->label('Quantity'), Select::make('customer_id') ->options(Customer::all()->pluck('company_name', 'id')) ->reactive() @@ -59,7 +60,8 @@ public static function table(Table $table): Table ->sortable() ->searchable(), TextColumn::make('contents'), - TextColumn::make('amount'), + TextColumn::make('amount') + ->label('Quantity'), TextColumn::make('order.customer.company_name') ->sortable() ->searchable(), diff --git a/app/Http/Controllers/PdfController.php b/app/Http/Controllers/PdfController.php new file mode 100644 index 0000000..af9ec13 --- /dev/null +++ b/app/Http/Controllers/PdfController.php @@ -0,0 +1,26 @@ +internal_id.'.pdf'); + + Pdf::view('pdf.invoice-report', ['invoiceReport' => $invoiceReport]) + ->withBrowsershot(function (Browsershot $browsershot) { + $browsershot->noSandbox(); + }) + ->margins(8, 8, 15, 8) + ->footerView('pdf.invoice-report-footer', ['invoiceReport' => $invoiceReport]) + ->save($url); + + return redirect($url); + } +} diff --git a/app/Models/InvoiceReport.php b/app/Models/InvoiceReport.php index 7e20a28..ae5418d 100644 --- a/app/Models/InvoiceReport.php +++ b/app/Models/InvoiceReport.php @@ -2,11 +2,11 @@ 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\BelongsToMany; -use Illuminate\Database\Eloquent\Relations\HasManyThrough; class InvoiceReport extends Model { @@ -28,8 +28,22 @@ public static function boot(): void { parent::boot(); - static::created(function ($model) { + static::created(function (InvoiceReport $model) { + + // Set ID after creation $model->attributes['internal_id'] = 'TN-INR-'.$model->id; + + // Associate all relevant invoices + $invoices = Invoice::whereBetween('date', [$model->date_start, $model->date_end]) + ->where('customer_id', $model->customer_id) + ->when($model->filter_paid, function ($query) { + $query->whereNot('status', InvoiceStatus::PAID); + }); + $model->invoices()->sync($invoices->pluck('id')->toArray()); + + $model->total = $model->invoices()->sum('total'); + + // Finally, save $model->save(); }); } @@ -44,8 +58,8 @@ public function invoices(): BelongsToMany return $this->BelongsToMany(Invoice::class); } - public function orders(): HasManyThrough + public function orders() { - return $this->hasManyThrough(Order::class, Invoice::class); + return $this->invoices()->with('orders')->get()->pluck('orders')->flatten()->unique('id'); } } diff --git a/public/invoice-tn-in-24-0017.pdf b/public/invoice-tn-in-24-0017.pdf new file mode 100644 index 0000000..f710344 Binary files /dev/null and b/public/invoice-tn-in-24-0017.pdf differ diff --git a/public/invoicereport-tn-inr-1.pdf b/public/invoicereport-tn-inr-1.pdf new file mode 100644 index 0000000..b6aa767 Binary files /dev/null and b/public/invoicereport-tn-inr-1.pdf differ diff --git a/public/invoicereport-tn-inr-3.pdf b/public/invoicereport-tn-inr-3.pdf new file mode 100644 index 0000000..c13784f Binary files /dev/null and b/public/invoicereport-tn-inr-3.pdf differ diff --git a/public/order-tn24-0029.pdf b/public/order-tn24-0029.pdf new file mode 100644 index 0000000..ed1907a Binary files /dev/null and b/public/order-tn24-0029.pdf differ diff --git a/resources/views/pdf/invoice-report-footer.blade.php b/resources/views/pdf/invoice-report-footer.blade.php new file mode 100644 index 0000000..7e7c2e0 --- /dev/null +++ b/resources/views/pdf/invoice-report-footer.blade.php @@ -0,0 +1,10 @@ + + +' \ No newline at end of file diff --git a/resources/views/pdf/invoice-report.blade.php b/resources/views/pdf/invoice-report.blade.php new file mode 100644 index 0000000..f0cafa9 --- /dev/null +++ b/resources/views/pdf/invoice-report.blade.php @@ -0,0 +1,90 @@ +@extends('layouts.pdf') + + + +
+ +
+ TOP NOTCH EMBROIDERY & DIGITIZING LTD. +
+
+ 108-618 EAST KENT AVE. SOUTH
+ VANCOUVER BC
+ (604) 871-9991
+ info@sewtopnotch.com
+ GST# 846025062RT0001
+
+ +
+ INVOICE REPORT +
+ + +
+
+
+ BILL TO +
+
+ {{$invoiceReport->customer->company_name}}
+ {{$invoiceReport->customer->billing_address_line_1}}
+ {{$invoiceReport->customer->billing_address_line_2}}
+
+
+ +
+
+ INVOICE REPORT # +
+
+ DATE +
+
+ BALANCE DUE +
+
+ +
+
+ {{$invoiceReport->internal_id}} +
+
+ {{Date::make($invoiceReport->created_at)->format('Y-d-m')}} +
+
+{{-- {{$invoiceReport->due_date}}--}} +
+
+
+ +
+ + + + + + + + + + + + + @foreach($invoiceReport->invoices as $invoice) + + + + + + + + + @endforeach + +
DateInvoiceSubtotalPSTGSTTotal
{{Date::make($invoice->created_at)->format('Y-m-d')}}{{$invoice->internal_id}}${{$invoice->subtotal}}{{!$invoice->pst_amount ? '-' : '$'.number_format($invoice->pst_amount, 2)}}${{number_format($invoice->gst_amount, 2)}}${{$invoice->total}}
+
+ diff --git a/resources/views/pdf/invoice.blade.php b/resources/views/pdf/invoice.blade.php index f0bb2e2..76e37da 100644 --- a/resources/views/pdf/invoice.blade.php +++ b/resources/views/pdf/invoice.blade.php @@ -70,10 +70,11 @@ RATE AMOUNT + @foreach($invoice->productServices as $service) - - + + {{$service->order->internal_po}} diff --git a/routes/web.php b/routes/web.php index 79707e5..4468d65 100644 --- a/routes/web.php +++ b/routes/web.php @@ -8,6 +8,7 @@ use App\Http\Controllers\OrderController; use App\Http\Controllers\OrderProductController; use App\Http\Controllers\PackingSlipController; +use App\Http\Controllers\PdfController; use App\Http\Controllers\ShippingEntryController; use Illuminate\Support\Facades\Route; @@ -17,24 +18,16 @@ Auth::routes(); -Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard'); - -Route::get('/management/{tab?}', [ManagementController::class, 'index'])->name('management.index'); - -// OrderProducts -Route::resource('order-products', OrderProductController::class); - -// Contacts -Route::resource('contacts', ContactController::class); -Route::post('/contacts/request-destroy', [ContactController::class, 'requestDestroy'])->name('contacts.requestDestroy'); - -Route::resource('packing-slips', PackingSlipController::class); - -Route::resource('shipping-entries', ShippingEntryController::class); - -Route::resource('orders', OrderController::class); +Route::get('/pdf/invoicereport/{id}', [PdfController::class, 'invoiceReport'])->name('pdf.invoice-report'); Route::get('orders/{order}/pdf', [OrderController::class, 'pdf'])->name('orders.pdf'); - Route::get('invoices/{invoice}/pdf', [InvoiceController::class, 'pdf'])->name('invoice.pdf'); - Route::get('customers/{customer}/pdf', [CustomerController::class, 'pdf'])->name('customer.pdf'); + +//Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard'); +//Route::get('/management/{tab?}', [ManagementController::class, 'index'])->name('management.index'); +//Route::resource('order-products', OrderProductController::class); +//Route::resource('contacts', ContactController::class); +//Route::post('/contacts/request-destroy', [ContactController::class, 'requestDestroy'])->name('contacts.requestDestroy'); +//Route::resource('packing-slips', PackingSlipController::class); +//Route::resource('shipping-entries', ShippingEntryController::class); +//Route::resource('orders', OrderController::class); diff --git a/todos b/todos index 614a31a..87f26dc 100644 --- a/todos +++ b/todos @@ -1,20 +1,34 @@ todo + +Orders +------- +- PDF pre-pro property +- Expand search to include code, placement, logo name + - how to expand search? +- Change order status from table > do thru checkboxes? +- Duplicate to new order (bulk actions too?) +- Validation +- Tabs for quotes, invoices, packingSlips? +- Make 'duplicate order'-button (edit page header) + Invoice Report -------------- -Model ?customer > table +- Calculate total due +- Finish pdf styling Quotes ------ - Add date? -Orders -------- -- Fix service type -- PDF pre-pro property -- Change order status from table > do thru checkboxes? -- Tabs for quotes, invoices, packingSlips? -- Fix total order price -- Duplicate to new order +Invoices +--------- +- Search by internal PO *and* customer PO +- In invoices table, search invoice by associated customer PO or internal PO + +Customer report +---------------- +- Save to PDF + Shipping Entries ----------------- @@ -26,15 +40,23 @@ Others - Way to set GST and PST - Remove service_type from ProductService? - order pdfs checkboxes don't tick no more -- duplicate order button (edit page header) -- badge for invoices - - finish invoice styling - add to invoice button on order page - customer name to invoice title / filename +- ability to change PST / GST amounts +- ability to change office address +- dynamically get office address in invoice / invoice reports (add top notch as customer?) + +- Save and close buttons at header (orders) + renamings: - order->total_service_price => subtotal - amount > quantity - amount_price > amount + + Discuss w/ James + ----------------- + Customer address system improvement (multiple addresses per customer instead of customer entry per address?) + Global search?