diff --git a/app/Filament/Resources/InvoiceReportResource.php b/app/Filament/Resources/InvoiceReportResource.php index e53aa51..fcadbe8 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,7 +105,7 @@ public static function getPages(): array { return [ 'index' => Pages\ListInvoiceReports::route('/'), - 'edit' => Pages\EditInvoiceReport::route('/{record}/edit'), + 'view' => Pages\ViewInvoiceReport::route('/{record}'), 'create' => Pages\CreateInvoiceReport::route('/create'), ]; } 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/OrderResource.php b/app/Filament/Resources/OrderResource.php index 4d6192c..dd9327e 100644 --- a/app/Filament/Resources/OrderResource.php +++ b/app/Filament/Resources/OrderResource.php @@ -151,10 +151,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 +188,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) diff --git a/app/Filament/Resources/OrderResource/Pages/ListOrders.php b/app/Filament/Resources/OrderResource/Pages/ListOrders.php index a22cb38..4ad5e60 100644 --- a/app/Filament/Resources/OrderResource/Pages/ListOrders.php +++ b/app/Filament/Resources/OrderResource/Pages/ListOrders.php @@ -36,6 +36,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()) 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..c9fd575 100644 --- a/app/Models/InvoiceReport.php +++ b/app/Models/InvoiceReport.php @@ -6,7 +6,6 @@ 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 +27,19 @@ 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); + $model->invoices()->sync($invoices->pluck('id')->toArray()); + + $model->total = $model->invoices()->sum('total'); + + // Finally, save $model->save(); }); } @@ -44,8 +54,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..2cc3ed0 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..aee4746 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/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..5d1b1ce 100644 --- a/todos +++ b/todos @@ -2,6 +2,10 @@ todo Invoice Report -------------- Model ?customer > table +- Calculate total due +- Filter paid implement +- Finish pdf styling +- Fix create page Quotes ------ @@ -13,8 +17,19 @@ Orders - 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 +- Duplicate to new order (bulk actions too?) +- Validation +- Expand search to include code, placement, logo name + +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 ----------------- @@ -29,12 +44,22 @@ Others - 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?