Work work

This commit is contained in:
Nisse Lommerde 2024-12-10 15:28:14 -08:00
parent 90ef3c9c29
commit 2451315c5f
52 changed files with 824 additions and 343 deletions

View File

@ -0,0 +1,14 @@
<?php
namespace App\Filament\Guest\Pages;
use Filament\Pages\Page;
class ContactUs extends Page
{
protected static ?string $navigationIcon = 'lucide-contact';
protected static ?int $navigationSort = 2;
protected static string $view = 'filament.guest.pages.contact-us';
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Filament\Guest\Pages;
use Filament\Pages\Page;
class Digitizing extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static ?int $navigationSort = -1;
protected static string $view = 'filament.guest.pages.digitizing';
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Filament\Guest\Pages;
use Filament\Pages\Page;
class Embroidery extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static ?int $navigationSort = 0;
protected static string $view = 'filament.guest.pages.embroidery';
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Filament\Guest\Pages;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;
use Filament\Pages\Page;
use Illuminate\Support\HtmlString;
class Home extends Page
{
protected static ?string $navigationIcon = 'lucide-house';
protected static ?int $navigationSort = -2;
protected static string $view = 'filament.guest.pages.home';
protected function getFormSchema(): array
{
return [
Section::make()
->schema([
Placeholder::make('homeContent')
->hiddenLabel()
->content(new HtmlString),
]),
];
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Filament\Guest\Pages;
use Filament\Pages\Page;
class Vinyl extends Page
{
protected static ?string $navigationIcon = 'heroicon-o-document-text';
protected static ?int $navigationSort = 1;
protected static string $view = 'filament.guest.pages.vinyl';
}

View File

@ -42,7 +42,7 @@ public static function table(Table $table): Table
->extraHeaderAttributes(['class' => 'w-full']),
Tables\Columns\TextColumn::make('subtotal')
->money('usd')
->money()
->alignRight()
->getStateUsing(function (Table $table, Model $record) {
return $record->getSubtotalAttribute(
@ -53,7 +53,7 @@ public static function table(Table $table): Table
Tables\Columns\TextColumn::make('gst')
->label('GST')
->money('usd')
->money()
->alignRight()
->getStateUsing(function (Table $table, Model $record) {
return $record->getGstAttribute(
@ -64,7 +64,7 @@ public static function table(Table $table): Table
Tables\Columns\TextColumn::make('pst')
->label('PST')
->money('usd')
->money()
->alignRight()
->getStateUsing(function (Table $table, Model $record) {
return $record->getPstAttribute(
@ -74,7 +74,7 @@ public static function table(Table $table): Table
}),
Tables\Columns\TextColumn::make('total')
->money('usd')
->money()
->weight('bold')
->alignRight()
->getStateUsing(function (Table $table, Model $record) {

View File

@ -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,24 +73,15 @@ public static function table(Table $table): Table
->label('End Date')
->date('Y-m-d'),
TextColumn::make('total')
->label('Balance Due')
->weight(FontWeight::Bold)
->money(),
])
->defaultSort('created_at', 'desc')
->defaultSort('id', 'desc')
->filters([
])
->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 getRelations(): array
@ -103,8 +95,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}'),
];
}
}

View File

@ -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(),
];
}
}

View File

@ -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,44 @@ 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')
->label('GST')
->alignRight()
->money(),
TextColumn::make('pst_amount')
->label('PST')
->alignRight()
->formatStateUsing(function ($state) {
return $state == 0.00 ? '-' : '$'.$state;
}),
TextColumn::make('total')
->label('Balance Due')
->alignRight()
->money()
->weight(FontWeight::Bold),
TextColumn::make('status'),
])
->filters([
//
])
->headerActions([
// Tables\Actions\CreateAction::make(),
Tables\Actions\AssociateAction::make(),
])
->defaultSort('invoices.id')
->actions([
Tables\Actions\DissociateAction::make(),
// Tables\Actions\EditAction::make(),
// Tables\Actions\DeleteAction::make(),
])
->bulkActions([
// Tables\Actions\BulkActionGroup::make([
// Tables\Actions\DeleteBulkAction::make(),
// ]),
]);
}
}

View File

@ -1,48 +0,0 @@
<?php
namespace App\Filament\Resources\InvoiceReportResource\RelationManagers;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;
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(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(),
]),
]);
}
}

View File

@ -10,6 +10,8 @@
use App\Models\Invoice;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Split;
@ -35,6 +37,8 @@ class InvoiceResource extends Resource
public static function form(Form $form): Form
{
return $form
->schema([
Group::make()
->schema([
Section::make([
Grid::make(2)
@ -55,11 +59,18 @@ public static function form(Form $form): Form
DatePicker::make('due_date'),
])
->columnSpan(2),
Select::make('status')
ToggleButtons::make('status')
->options(InvoiceStatus::class)
->searchable()
->required()
->default(InvoiceStatus::UNPAID),
->inline()
->default(InvoiceStatus::UNPAID)
->columnSpan(2),
// Select::make('status')
// ->options(InvoiceStatus::class)
// ->searchable()
// ->required()
// ->default(InvoiceStatus::UNPAID),
])->columnSpan(2),
Grid::make(1)
@ -84,8 +95,26 @@ public static function form(Form $form): Form
])->columnSpan(1),
])
->columns(3)
->columnSpan(3),
])->columns(3);
->columnSpan(2),
Section::make()
->schema([
Placeholder::make('created_at')
->label('Created at')
->content(fn (Invoice $record): ?string => $record->created_at?->diffForHumans()),
Placeholder::make('updated_at')
->label('Last modified at')
->content(fn (Invoice $record): ?string => $record->updated_at?->diffForHumans()),
])
->columnSpan(1)
->hidden(fn (?Invoice $record) => $record === null),
])
->columns(3)
->columnSpan(2),
])->columns(2);
}
public static function table(Table $table): Table
@ -97,22 +126,33 @@ public static function table(Table $table): Table
->fontFamily('mono')
->color('primary')
->sortable()
->searchable(),
->searchable(query: function (Builder $query, $search) {
return $query->where('internal_id', 'like', "%{$search}%")
->orWhereHas('orders', function (Builder $query) use ($search) {
return $query->where('customer_po', 'like', "%{$search}%")
->orWhere('internal_po', 'like', "%{$search}%");
});
}),
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')
->money()
->alignRight(),
Tables\Columns\TextColumn::make('gst_amount')
->label('GST')
->money('USD')
->money()
->alignRight(),
Tables\Columns\TextColumn::make('pst_amount')
->label('PST')
->formatStateUsing(function ($state) {
@ -120,7 +160,8 @@ public static function table(Table $table): Table
})
->alignRight(),
Tables\Columns\TextColumn::make('total')
->money('USD')
->label('Balance')
->money()
->weight('bold')
->alignRight(),
Tables\Columns\TextColumn::make('status')
@ -157,11 +198,10 @@ public static function table(Table $table): Table
'status',
])
->defaultSort('created_at', 'desc')
->defaultSort('id', 'desc')
->actions([
Tables\Actions\EditAction::make(),
//todo: generate report pdf
])
->bulkActions([

View File

@ -23,4 +23,15 @@ protected function getHeaderActions(): array
->icon('lucide-trash-2'),
];
}
// protected function after(array $data): array
// {
// $invoice = Invoice::findOrFail($data['id']);
//
// if ($invoice->invoiceReports()->count() > 0) {
// foreach ($invoice->invoiceReports as $report) {
// $report->updateTotalBalance();
// }
// }
// }
}

View File

@ -2,9 +2,11 @@
namespace App\Filament\Resources\InvoiceResource\RelationManagers;
use App\Filament\Resources\OrderResource;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Support\Enums\FontFamily;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
@ -27,18 +29,27 @@ 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('internal_po')
->label('Internal PO')
->color('primary')
->fontFamily(FontFamily::Mono),
Tables\Columns\TextColumn::make('customer_po')
->label('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'),
->money(),
])
->filters([
//

View File

@ -9,9 +9,14 @@
use App\Models\Contact;
use App\Models\Customer;
use App\Models\Order;
use App\Models\OrderProduct;
use App\Models\ProductService;
use App\Models\ServiceFile;
use App\Models\ServiceType;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
@ -20,15 +25,16 @@
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons;
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
{
@ -41,8 +47,9 @@ class OrderResource extends Resource
public static function form(Form $form): Form
{
return $form->schema([
Group::make()
->schema([
Section::make([
Grid::make(1)
->schema([
Select::make('order_type')
@ -50,22 +57,22 @@ public static function form(Form $form): Form
->options(OrderType::class)
->searchable(),
Split::make([
// Split::make([
Select::make('customer_id')
->required()
->label('Customer')
->options(Customer::all()->pluck('company_name', 'id'))
->reactive()
// ->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(),
]),
// 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()
@ -113,15 +120,35 @@ public static function form(Form $form): Form
]),
])->columnSpan(1),
])->columns(2),
])->columns(2)
->columnSpan(fn (?Order $record) => $record === null ? 4 : 3),
Section::make()
->schema([
Placeholder::make('created_at')
->label('Created')
->content(fn (Order $record): ?string => $record->created_at?->diffForHumans()),
Placeholder::make('updated_at')
->label('Last modified')
->content(fn (Order $record): ?string => $record->updated_at?->diffForHumans()),
])
->columnSpan(1)
->hidden(fn (?Order $record) => $record === null),
])
->columns(4)
->columnSpan(2),
TableRepeater::make('order_products')
->label('Garments')
->schema([
TextInput::make('sku'),
TextInput::make('sku')
->datalist(OrderProduct::all()->unique('sku')->pluck('sku')->toArray()),
TextInput::make('product_name')
->datalist(OrderProduct::all()->unique('product_name')->pluck('product_name')->toArray())
->required(),
TextInput::make('color'),
TextInput::make('color')
->datalist(OrderProduct::all()->unique('color')->pluck('color')->toArray()),
Cluster::make([
TextInput::make('xs')
->placeholder('xs'),
@ -151,13 +178,27 @@ 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')
->datalist(ProductService::all()->unique('placement')->pluck('placement')->toArray())
->columnSpan(3),
TextInput::make('serviceFileName')
->datalist(ServiceFile::all()->unique('name')->pluck('name')->toArray())
->columnSpan(3)
->label('Logo Name'),
TextInput::make('serviceFileSetupNumber')
@ -170,36 +211,18 @@ public static function form(Form $form): Form
TextInput::make('serviceFileHeight')
->prefix('h'),
])
->label('Dimensions')
->label('Dimensions (inches)')
->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')
->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));
})
->columnSpan(2),
TextInput::make('total_price')
->prefix('$')
->readOnly()
->columnSpan(2),
]),
@ -207,6 +230,7 @@ public static function form(Form $form): Form
->schema([
TextInput::make('serviceFileCode')
->label('Code')
->datalist(ServiceFile::all()->unique('code')->pluck('code')->toArray())
->columnSpan(1)
->placeholder('A1234'),
@ -241,7 +265,17 @@ public static function table(Table $table): Table
->label('Internal PO')
->fontFamily('mono')
->color('info')
->searchable()
->searchable(query: function (Builder $query, $search) {
return $query
->where('internal_po', 'like', "%{$search}%")
->orWhereHas('productServices', function (Builder $query) use ($search) {
return $query->where('placement', 'like', "%{$search}%")
->orWhereHas('serviceFile', function (Builder $query) use ($search) {
return $query->where('code', 'like', "%{$search}%")
->orWhere('name', 'like', "%{$search}%");
});
});
})
->sortable(),
TextColumn::make('customer.company_name')
@ -272,9 +306,7 @@ public static function table(Table $table): Table
->searchable()
->sortable(),
])
->defaultSort('order_date', 'desc')
->filters([
Tables\Filters\Filter::make('order_date')
->form([
@ -293,12 +325,33 @@ public static function table(Table $table): Table
);
}),
], )
->actions([
Tables\Actions\EditAction::make(),
])
->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(),
]),

View File

@ -148,6 +148,9 @@ protected function getHeaderActions(): array
->label('Save changes')
->action('save')
->icon('lucide-save'),
Actions\ReplicateAction::make()
->icon('lucide-copy')
->color('info'),
Action::make('print')
->icon('lucide-printer')
->url(fn (Order $record) => route('orders.pdf', $record))
@ -156,4 +159,9 @@ protected function getHeaderActions(): array
->icon('lucide-trash-2'),
];
}
// protected function getRedirectUrl(): string
// {
// return $this->previousUrl ?? $this->getResource()::getUrl('index');
// }
}

View File

@ -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())
@ -72,26 +85,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()),
];
}
}

View File

@ -7,6 +7,7 @@
use App\Models\Order;
use App\Models\PackingSlip;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
@ -30,19 +31,29 @@ public static function form(Form $form): Form
{
return $form
->schema([
DatePicker::make('date_received'),
TextInput::make('amount'),
Section::make([
DatePicker::make('date_received')
->default(today())
->required(),
// TextInput::make('amount')
// ->label('Quantity')
// ->required(),
Select::make('customer_id')
->label('Customer')
->options(Customer::all()->pluck('company_name', 'id'))
->reactive()
->searchable(),
Select::make('order_id')
->label('Order')
->required()
->options(fn ($get): array => Order::where('customer_id', $get('customer_id') ?? null)
->get()
->pluck('customer_po', 'id')
->toArray())
->searchable(),
TextArea::make('contents'),
])
->columns(2),
]);
}
@ -58,8 +69,10 @@ public static function table(Table $table): Table
->color('code')
->sortable()
->searchable(),
TextColumn::make('contents'),
TextColumn::make('amount'),
TextColumn::make('contents')
->extraHeaderAttributes(['class' => 'w-full']),
// TextColumn::make('amount')
// ->label('Quantity'),
TextColumn::make('order.customer.company_name')
->sortable()
->searchable(),

View File

@ -6,10 +6,10 @@
use App\Models\Customer;
use App\Models\Order;
use App\Models\Quote;
use Filament\Forms\Components\RichEditor;
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;
@ -47,9 +47,9 @@ public static function form(Form $form): Form
->searchable(),
])->columnSpan(2),
Textarea::make('body')
->columnSpan(2)
->rows(8),
RichEditor::make('body')
->columnSpan(2),
// ->rows(8),
]),
])->columns(3);
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers;
use App\Models\InvoiceReport;
use Spatie\Browsershot\Browsershot;
use Spatie\LaravelPdf\Facades\Pdf;
class PdfController extends Controller
{
public function invoiceReport(int $id)
{
$invoiceReport = InvoiceReport::find($id);
$url = strtolower('invoicereport-'.$invoiceReport->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);
}
}

View File

@ -8,10 +8,11 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\SoftDeletes;
class Invoice extends Model
{
use HasFactory;
use HasFactory, SoftDeletes;
protected $fillable = [
'customer_id',
@ -41,7 +42,7 @@ public static function boot(): void
parent::boot();
static::created(function ($model) {
$model->attributes['internal_id'] = $model->generateInternalId($model->id);
$model->attributes['internal_id'] = 'TN4'.str_pad($model->id, 4, '0', STR_PAD_LEFT);
$model->save();
});
}
@ -54,13 +55,13 @@ public function setStatus(InvoiceStatus $status)
}
}
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 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
{

View File

@ -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
{
@ -21,19 +21,47 @@ class InvoiceReport extends Model
'subtotal',
'pst',
'gst',
// 'total',
];
protected $appends = [
'total',
];
protected $casts = [
'date_start' => 'date',
'date_end' => 'date',
];
public static function boot(): void
{
parent::boot();
static::created(function ($model) {
$model->attributes['internal_id'] = 'TN-INR-'.$model->id;
static::created(function (InvoiceReport $model) {
// Set ID after creation
$model->attributes['internal_id'] = 'TNR'.str_pad($model->id, 4, '0', STR_PAD_LEFT);
// 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()->where('status', 'UNPAID')->sum('total');
// Finally, save
$model->save();
});
}
public function getTotalAttribute()
{
return $this->invoices()->sum('total');
}
public function customer(): BelongsTo
{
return $this->belongsTo(Customer::class);
@ -44,8 +72,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');
}
}

View File

@ -29,11 +29,6 @@ class ProductService extends Model
'price',
];
// public function getServiceType(): string
// {
// return $this->serviceType->name ?? '';
// }
public function getPriceAttribute(): float
{
return number_format($this->amount * $this->amount_price, 2);

View File

@ -0,0 +1,49 @@
<?php
namespace App\Observers;
use App\Models\Invoice;
use Illuminate\Support\Facades\Log;
class InvoiceObserver
{
/**
* Handle the Invoice "created" event.
*/
public function created(Invoice $invoice): void
{
Log::debug('Invoice created');
}
/**
* Handle the Invoice "updated" event.
*/
public function updated(Invoice $invoice): void
{
\Log::debug('Invoice updated!');
}
/**
* Handle the Invoice "deleted" event.
*/
public function deleted(Invoice $invoice): void
{
//
}
/**
* Handle the Invoice "restored" event.
*/
public function restored(Invoice $invoice): void
{
//
}
/**
* Handle the Invoice "force deleted" event.
*/
public function forceDeleted(Invoice $invoice): void
{
//
}
}

View File

@ -0,0 +1,56 @@
<?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 Filament\Widgets;
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 GuestPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->id('guest')
->path('')
->colors([
'primary' => Color::Blue,
])
->discoverResources(in: app_path('Filament/Guest/Resources'), for: 'App\\Filament\\Guest\\Resources')
->discoverPages(in: app_path('Filament/Guest/Pages'), for: 'App\\Filament\\Guest\\Pages')
->pages([
// Pages\Dashboard::class,
])
->discoverWidgets(in: app_path('Filament/Guest/Widgets'), for: 'App\\Filament\\Guest\\Widgets')
->widgets([
// Widgets\AccountWidget::class,
// Widgets\FilamentInfoWidget::class,
])
->middleware([
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
AuthenticateSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
DisableBladeIconComponents::class,
DispatchServingFilamentEvent::class,
])
->authMiddleware([
// Authenticate::class,
]);
}
}

View File

@ -3,4 +3,5 @@
return [
App\Providers\AppServiceProvider::class,
App\Providers\Filament\AdminPanelProvider::class,
App\Providers\Filament\GuestPanelProvider::class,
];

View File

@ -17,11 +17,7 @@ public function definition(): array
'customer_id' => Customer::all()->shuffle()->first()->id,
'date_start' => Carbon::now()->subYear(),
'date_end' => Carbon::now(),
'filter_paid' => $this->faker->boolean(90),
/* 'subtotal' => $this->faker->randomFloat(),
'pst' => $this->faker->randomFloat(),
'gst' => $this->faker->randomFloat(),
'total' => $this->faker->randomFloat(),*/
'filter_paid' => $this->faker->boolean(40),
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
];

View File

@ -25,6 +25,7 @@ public function up(): void
$table->date('date')->default(today());
$table->date('due_date')->nullable();
$table->softDeletes();
$table->timestamps();
});
}

View File

@ -20,7 +20,7 @@ public function up(): void
$table->float('subtotal', 2)->default(0);
$table->float('pst', 2)->default(0);
$table->float('gst', 2)->default(0);
$table->float('total', 2)->default(0);
// $table->float('total', 2)->default(0);
$table->timestamps();
});

View File

@ -26,7 +26,5 @@ public function run(): void
$invoice->calculateTotals();
}
\Log::debug(Invoice::all());
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/order-tn24-0029.pdf Normal file

Binary file not shown.

BIN
public/order-tn24-0093.pdf Normal file

Binary file not shown.

BIN
public/order-tn24-0132.pdf Normal file

Binary file not shown.

BIN
public/order-tn24-0179.pdf Normal file

Binary file not shown.

View File

@ -0,0 +1,5 @@
<x-filament-panels::page>
</x-filament-panels::page>

View File

@ -0,0 +1,3 @@
<x-filament-panels::page>
</x-filament-panels::page>

View File

@ -0,0 +1,3 @@
<x-filament-panels::page>
</x-filament-panels::page>

View File

@ -0,0 +1,3 @@
<x-filament-panels::page>
{{ $this->form }}
</x-filament-panels::page>

View File

@ -0,0 +1,3 @@
<x-filament-panels::page>
</x-filament-panels::page>

View File

@ -0,0 +1,10 @@
<style>
* {
font-size: 12px;
margin: 10px 5px;
}
</style>
<footer>
{{$invoiceReport->internal_id}}, page @pageNumber of @totalPages
</footer>'

View File

@ -0,0 +1,106 @@
@extends('layouts.pdf')
<style>
* {
font-size: 0.9rem;
}
</style>
<div class="container-fluid pt-4 font-serif" style="">
<div class="fw-bold">
TOP NOTCH EMBROIDERY & DIGITIZING LTD.
</div>
<div>
108-618 EAST KENT AVE. SOUTH <br>
VANCOUVER BC <br>
(604) 871-9991 <br>
info@sewtopnotch.com <br>
GST# 846025062RT0001 <br>
</div>
<div class="fs-3 fw-bold text-primary mt-2">
INVOICE STATEMENT
</div>
<div class="d-flex flex-row">
<div class="me-auto">
<div class="fw-bold">
BILL TO
</div>
<div>
{{$invoiceReport->customer->company_name}} <br>
{{$invoiceReport->customer->billing_address_line_1}} <br>
{{$invoiceReport->customer->billing_address_line_2}} <br>
</div>
</div>
<div class="text-end pe-2">
<div class="fw-bold">
INVOICE REPORT #
</div>
<div class="fw-bold">
DATE
</div>
<div class="fw-bold">
DATE FROM
</div>
<div class="fw-bold">
DATE TO
</div>
<br>
<div class="fw-bold">
TOTAL BALANCE DUE
</div>
</div>
<div>
<div>
{{$invoiceReport->internal_id}}
</div>
<div>
{{Date::make($invoiceReport->created_at)->format('Y-m-d')}}
</div>
<div>
{{$invoiceReport->date_start->format('Y-m-d')}}
</div>
<div>
{{$invoiceReport->date_end->format('Y-m-d')}}
</div>
<br>
<div>
${{number_format($invoiceReport->total, 2)}}
</div>
</div>
</div>
<br>
<table class="table table-sm table-striped">
<tr>
<th>Date</th>
<th>Invoice</th>
<th class="text-end">Subtotal</th>
<th class="text-end">GST</th>
<th class="text-end">PST</th>
<th class="text-end">Balance</th>
<th class="text-end">Status</th>
</tr>
@foreach($invoiceReport->invoices as $invoice)
<tr>
<td>{{Date::make($invoice->created_at)->format('Y-m-d')}}</td>
<td>{{$invoice->internal_id}}</td>
<td class="text-end">${{$invoice->subtotal}}</td>
<td class="text-end">${{number_format($invoice->gst_amount, 2)}}</td>
<td class="text-end">{{!$invoice->pst_amount ? '-' : '$'.number_format($invoice->pst_amount, 2)}}</td>
<td class="text-end">${{$invoice->total}}</td>
<td class="text-end">{{$invoice->status->value}}</td>
</tr>
@endforeach
</table>
</div>

View File

@ -70,10 +70,11 @@
<th>RATE</th>
<th>AMOUNT</th>
</tr>
@foreach($invoice->productServices as $service)
<tr>
<td>
<span style="font-size: 0.9rem">
<td class="text-nowrap">
<span style="font-size: 0.8rem">
{{$service->order->internal_po}}
</span>
</td>

View File

@ -108,8 +108,9 @@
<div class="fs-6">
PO#
</div>
<div class="fs-6 text-info">
<div class="fs-6 text-primary">
{{$order->customer_po}}
@if($order->pre_production)<span class="text-danger"> (pre-pro)</span>@endif()
</div>
</div>
@ -118,7 +119,7 @@
<div class="d-inline-flex gap-2">
<div class="h-auto">
<div class="border border-1 border-opacity-50 mt-1">
<div class="@if(!$order->purchased_garments) opacity-0 @endif">
<div class="@if(!$order->garments) opacity-0 @endif">
<x-bi-check/>
</div>
</div>
@ -170,14 +171,16 @@
</tbody>
</table>
</div>
<div class="row mt-3 text-end" style="font-size: 11px">
<div class="row mt-3 text-end" style="font-size: 12px">
<div class="col">
TOTAL QUANTITY: {{$order->totalProductQuantity}}
</div>
</div>
<br>
<!-- Services Table -->
<div class="row mt-3">
<div class="row mt-2">
<table class="table table-striped" style="font-size: 11px;">
<thead class="opacity-50 fw-normal">
<th>Placement & Service</th>
@ -227,6 +230,8 @@
</table>
</div>
<br>
<div class="row mt-3">
{{$order->notes}}
</div>

View File

@ -8,33 +8,26 @@
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;
Route::get('/', function () {
return redirect()->route('dashboard');
});
//Route::get('/', function () {
// return redirect()->route('dashboard');
//});
//
//Auth::routes();
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);

61
todos
View File

@ -1,40 +1,53 @@
todo
Invoice Report
--------------
Model ?customer > table
Guest Panel
-----------
Make info pages
Show customer order status
Invoice Reports
---------------
Filter paid doesn't work?
Orders
-------
- Validation
- Tabs for quotes, invoices, packingSlips?
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
Customer report
----------------
- Save to PDF
Shipping Entries
-----------------
- Clickable URL in title
Invoices
--------
Due date doesn't update?
Others
-------
- ServiceFile findOrCreate
- 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
- Change price calculations to round to two
- customer name to invoice title / filename
- ability to change PST / GST amounts
- ability to change office address (see below)
- dynamically get office address in invoice / invoice reports (add top notch as customer?)
Potentials
-----------
- ServiceFile findOrCreate?
- add to invoice button on order page?
- 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?