Compare commits

..

No commits in common. "main" and "0.99.1" have entirely different histories.
main ... 0.99.1

147 changed files with 1974 additions and 2906 deletions

View File

@ -1,25 +0,0 @@
# .dockerignore
/deploy/docker-compose.yml
/deploy/Dockerfile
/.phpunit.cache
/node_modules
/public/build
/public/hot
/public/storage
/public/bucket
/storage/*.key
/vendor
.env
.env.example
.env.backup
.env.production
.phpunit.result.cache
Homestead.json
Homestead.yaml
auth.json
npm-debug.log
yarn-error.log
/.fleet
/.idea
/.vscode
.git

View File

@ -1,27 +0,0 @@
name: Deploy
# Trigger the workflow on push and
# pull request events on the production branch
on:
push:
branches:
- main
pull_request:
branches:
- main
# Authenticate to the the server via ssh
# and run our deployment script
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
port: ${{ secrets.PORT }}
key: ${{ secrets.SSHKEY }}
script: "cd /var/www/sewtopnotch.com && ./.scripts/deploy.sh"

1
.gitignore vendored
View File

@ -108,4 +108,3 @@ fabric.properties
.directory
public
_ide_helper.php
public/build

View File

@ -1,32 +0,0 @@
#!/bin/bash
set -e
echo "Deployment started ..."
# Enter maintenance mode or return true
# if already is in maintenance mode
(php artisan down) || true
# Pull the latest version of the app from main branch
git pull origin main
# Install composer dependencies
composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
# Clear the old cache
php artisan clear-compiled
# Recreate cache
php artisan optimize
# Npm stuff (ci will fail when lockfile modified)
npm ci
npm run build
# Run database migrations
php artisan migrate --force
# Exit maintenance mode
php artisan up
echo "Deployment finished!"

View File

@ -1,8 +1,70 @@
# Changelog
https://github.com/spatie/laravel-pdf/discussions/90
**2025-03-11**
- Fixed #122 - Non-admins can see payments
- Fixed #107 - Fix dashboard
- Fixed #118 - Improved customer form and re-add 'create customer' to order form
- Fixed #117 - Draft orders should not show up in order tabs
- Fixed #116 - 'Ready for invoice'-badge shows 0 instead of hiding
for spatie/pdf stuff
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## About Laravel
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications.
## Learning Laravel
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
## Laravel Sponsors
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
### Premium Partners
- **[Vehikl](https://vehikl.com/)**
- **[Tighten Co.](https://tighten.co)**
- **[WebReinvent](https://webreinvent.com/)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
- **[Cyber-Duck](https://cyber-duck.co.uk)**
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
- **[Jump24](https://jump24.co.uk)**
- **[Redberry](https://redberry.international/laravel/)**
- **[Active Logic](https://activelogic.com)**
- **[byte5](https://byte5.de)**
- **[OP.GG](https://op.gg)**
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

View File

@ -4,59 +4,20 @@
enum IconEnum: string
{
// Sidebar Icons
case DEFAULT = 'heroicon-o-rectangle-stack';
case INVOICE = 'lucide-file-text';
case ORDER = 'lucide-shopping-cart';
case QUOTE = 'lucide-quote';
case CUSTOMER = 'lucide-building';
case PACKING_SLIP = 'lucide-package';
case SHIPPING_ENTRY = 'lucide-truck';
case PAYMENTS = 'lucide-hand-coins';
case USER = 'lucide-users';
case TAX_RATE = 'lucide-circle-dollar-sign';
case PRODUCT_SERVICE = 'heroicon-o-rectangle';
case CUSTOMER_SALES = 'lucide-book-user';
case INVOICE_REPORT = 'lucide-file-spreadsheet';
case DEFAULT = 'heroicon-o-rectangle-stack';
case INVOICE = 'lucide-file-text';
case ORDER = 'lucide-shopping-cart';
case QUOTE = 'lucide-quote';
case CUSTOMER = 'lucide-building';
case PACKING_SLIP = 'lucide-package';
case SHIPPING_ENTRY = 'lucide-truck';
case USER = 'lucide-users';
case TAX_RATE = 'lucide-circle-dollar-sign';
case DISTRIBUTE_PAYMENTS = 'lucide-rotate-cw';
// Tabs
case TAB_ALL = 'lucide-layout-grid';
case TAB_OVERDUE = 'lucide-calendar-clock';
// Action Icons
case PRINT = 'lucide-printer';
case TRASH = 'lucide-trash-2';
case SAVE = 'lucide-save';
case COPY = 'lucide-copy';
case NEW = 'lucide-plus';
// Invoice Status
case UNPAID = 'lucide-circle-x';
case PARTIALLY_PAID = 'lucide-circle-minus';
case PAID = 'lucide-circle-check';
case VOID = 'lucide-circle-slash';
// Order Attributes
case NEW_ART = 'lucide-brush';
case REPEAT = 'lucide-files';
case RUSH = 'lucide-bell-ring';
case EVENT = 'lucide-calendar-range';
case DIGITIZING = 'lucide-computer';
case GARMENTS = 'lucide-shirt';
case SUPPLIED_FILE = 'lucide-file-check';
// Order Status
case DRAFT = 'lucide-pencil';
case APPROVED = 'lucide-check-check';
case PRODUCTION = 'lucide-refresh-cw';
case SHIPPED = 'lucide-send';
case INVOICING = 'lucide-calendar';
case INVOICED = 'lucide-credit-card';
// Shipping Types (THEY_SHIP => SHIPPING_ENTRY)
case WE_SHIP = 'lucide-house';
case PICKUP = 'lucide-handshake';
case SHIPPING_OTHER = 'lucide-ellipsis';
case PRODUCT_SERVICE = 'heroicon-o-rectangle';
case CUSTOMER_SALES = 'lucide-book-user';
case INVOICE_REPORT = 'lucide-files';
case TAB_ALL = 'lucide-layout-grid';
case TAB_OVERDUE = 'lucide-calendar-clock';
case TAB_UNPRINTED = 'lucide-printer';
}

View File

@ -8,38 +8,30 @@
enum InvoiceStatus: string implements HasColor, HasIcon, HasLabel
{
case UNPAID = 'not_paid';
case PARTIALLY_PAID = 'partially_paid';
case PAID = 'paid';
case VOID = 'void';
case UNPAID = 'Not paid';
case PAID = 'Paid';
case VOID = 'Void';
public function getLabel(): string
public function getLabel(): ?string
{
return match ($this) {
self::UNPAID => 'Not paid',
self::PARTIALLY_PAID => 'Partially paid',
self::PAID => 'Paid',
self::VOID => 'Void',
};
return $this->value;
}
public function getColor(): string|array|null
{
return match ($this) {
self::UNPAID => 'danger',
self::PARTIALLY_PAID => 'warning',
self::PAID => 'success',
self::VOID => 'gray'
self::UNPAID => 'warning',
self::PAID => 'success',
self::VOID => 'gray'
};
}
public function getIcon(): ?string
{
return match ($this) {
self::UNPAID => IconEnum::UNPAID->value,
self::PARTIALLY_PAID => IconEnum::PARTIALLY_PAID->value,
self::PAID => IconEnum::PAID->value,
self::VOID => IconEnum::VOID->value,
self::UNPAID => 'lucide-circle-x',
self::PAID => 'lucide-circle-check',
self::VOID => 'lucide-circle-slash',
};
}
}

View File

@ -23,13 +23,13 @@ public function getLabel(): ?string
public function getIcon(): ?string
{
return match ($this) {
self::new_art => IconEnum::NEW_ART->value,
self::repeat => IconEnum::REPEAT->value,
self::rush => IconEnum::RUSH->value,
self::event => IconEnum::EVENT->value,
self::digitizing => IconEnum::DIGITIZING->value,
self::garments => IconEnum::GARMENTS->value,
self::supplied_file => IconEnum::SUPPLIED_FILE->value,
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',
};
}
}

View File

@ -8,46 +8,36 @@
enum OrderStatus: string implements HasColor, HasIcon, HasLabel
{
case DRAFT = 'draft';
case APPROVED = 'approved';
case PRODUCTION = 'production';
case SHIPPED = 'shipped';
case READY_FOR_INVOICE = 'ready_for_invoice';
case INVOICED = 'invoiced';
case DRAFT = 'Draft';
case APPROVED = 'Approved';
case PRODUCTION = 'Production';
case SHIPPED = 'Shipped';
case INVOICED = 'Invoiced';
public function getLabel(): string
public function getLabel(): ?string
{
return match ($this) {
self::DRAFT => 'Draft',
self::APPROVED => 'Approved',
self::PRODUCTION => 'Production',
self::SHIPPED => 'Shipped',
self::READY_FOR_INVOICE => 'Ready for Invoice',
self::INVOICED => 'Invoiced',
};
return $this->value;
}
public function getColor(): string|array|null
{
return match ($this) {
self::DRAFT => 'gray',
self::APPROVED => 'success',
self::PRODUCTION => 'primary',
self::SHIPPED => 'warning',
self::READY_FOR_INVOICE => 'invoicing',
self::INVOICED => 'invoiced',
self::DRAFT => 'gray',
self::APPROVED => 'success',
self::PRODUCTION => 'primary',
self::SHIPPED => 'warning',
self::INVOICED => 'invoiced',
};
}
public function getIcon(): ?string
{
return match ($this) {
self::DRAFT => IconEnum::DRAFT->value,
self::APPROVED => IconEnum::APPROVED->value,
self::PRODUCTION => IconEnum::PRODUCTION->value,
self::SHIPPED => IconEnum::SHIPPED->value,
self::READY_FOR_INVOICE => IconEnum::INVOICING->value,
self::INVOICED => IconEnum::INVOICED->value,
self::DRAFT => 'lucide-pencil',
self::APPROVED => 'lucide-check-check',
self::PRODUCTION => 'lucide-refresh-cw',
self::SHIPPED => 'lucide-send',
self::INVOICED => 'lucide-credit-card',
};
}
}

View File

@ -6,20 +6,14 @@
enum OrderType: string implements HasLabel
{
case EMB = 'embroidery';
case SCP = 'screen_printing';
case DTG = 'direct_to_garment';
case VINYL = 'vinyl';
case MISC = 'misc';
case EMB = 'Embroidery';
case SCP = 'Screen printing';
case DTG = 'Direct-to-garment';
case VINYL = 'Vinyl';
case MISC = 'Misc';
public function getLabel(): string
public function getLabel(): ?string
{
return match ($this) {
self::EMB => 'Embroidery',
self::SCP => 'Screen printing',
self::DTG => 'Direct-to-garment',
self::VINYL => 'Vinyl',
self::MISC => 'Misc',
};
return $this->value;
}
}

View File

@ -20,10 +20,10 @@ public function getLabel(): ?string
public function getIcon(): ?string
{
return match ($this) {
self::THEY_SHIP => IconEnum::SHIPPING_ENTRY->value,
self::WE_SHIP => IconEnum::WE_SHIP->value,
self::PICKUP => IconEnum::PICKUP->value,
self::OTHER => IconEnum::SHIPPING_OTHER->value,
self::THEY_SHIP => 'lucide-truck',
self::WE_SHIP => 'lucide-house',
self::PICKUP => 'lucide-handshake',
self::OTHER => 'lucide-ellipsis'
};
}
}

View File

@ -2,7 +2,6 @@
namespace App\Filament\Admin\Resources\ContactResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Resources\ContactResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
@ -14,8 +13,7 @@ class ListContacts extends ListRecords
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->icon(IconEnum::NEW->value),
Actions\CreateAction::make(),
];
}
}

View File

@ -4,15 +4,15 @@
use App\Enums\IconEnum;
use App\Models\Customer;
use App\Models\Invoice;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Support\Enums\FontWeight;
use Filament\Tables;
use Filament\Tables\Columns\Summarizers\Sum;
use Filament\Tables\Columns\Summarizers\Summarizer;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Query\Builder;
class CustomerReportResource extends Resource
{
@ -41,66 +41,68 @@ public static function table(Table $table): Table
Tables\Columns\TextColumn::make('company_name')
->label('Customer')
->sortable()
// ->searchable()
->searchable()
->extraHeaderAttributes(['class' => 'w-full']),
Tables\Columns\TextColumn::make('invoices.subtotal')
->label('Subtotal')
Tables\Columns\TextColumn::make('subtotal')
->money()
// ->summarize(Summarizer::make()->using(function ($query, Table $table) {
// $createdAt = $table->getfilter('created_at')->getstate()['created_at'] ?? '1900-01-01';
// $createdUntil = $table->getfilter('created_until')->getstate()['created_until'] ?? '2100-01-01';
//
// $invoiceSum = invoice::wherebetween('date', [$createdAt, $createdUntil])->sum('subtotal');
//
// return '$'.number_format(round($invoiceSum, 2), 2, '.', ',');
// }))
->alignRight()
->getStateUsing(fn (Table $table, Model $record) => $record->invoices()->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('subtotal'))
->summarize(Sum::make('subtotal')
->label('')
->money()
->using(fn (Table $table, $query) => $query->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('subtotal'))
),
->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('invoices.hst_amount')
->label('HST')
->money()
->alignRight()
->getStateUsing(fn (Table $table, Model $record) => $record->invoices()->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('hst_amount'))
->summarize(Sum::make('hst_amount')
->label('')
->money()
->using(fn (Table $table, Builder $query) => $query->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('hst_amount'))
),
Tables\Columns\TextColumn::make('invoices.gst_amount')
Tables\Columns\TextColumn::make('gst')
->label('GST')
->money()
->alignRight()
->getStateUsing(fn (Table $table, Model $record) => $record->invoices()->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('gst_amount'))
->summarize(Sum::make('gst_amount')
->label('')
->money()
->using(fn (Table $table, $query) => $query->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('gst_amount'))
),
->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('invoices.pst_amount')
Tables\Columns\TextColumn::make('pst')
->label('PST')
->money()
->alignRight()
->getStateUsing(fn (Table $table, Model $record) => $record->invoices()->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('pst_amount'))
->summarize(Sum::make('pst_amount')
->label('')
->money()
->using(fn (Table $table, $query) => $query->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('pst_amount'))
),
->getStateUsing(function (Table $table, Customer $record) {
return $record->getPstAttribute(
$table->getFilter('created_at')->getState()['created_at'],
$table->getFilter('created_until')->getState()['created_until']
);
}),
Tables\Columns\TextColumn::make('invoices.total')
->label('Total')
Tables\Columns\TextColumn::make('total')
->money()
// ->summarize(summarizer::make()->using(function ($query, table $table) {
// $createdAt = $table->getfilter('created_at')->getstate()['created_at'] ?? '1900-01-01';
// $createdUntil = $table->getfilter('created_until')->getstate()['created_until'] ?? '2100-01-01';
//
// $invoiceSum = invoice::wherebetween('date', [$createdAt, $createdUntil])->sum('total');
//
// return '$'.number_format(round($invoiceSum, 2), 2, '.', ',');
// }))
->weight(FontWeight::Bold)
->alignRight()
->getStateUsing(fn (Table $table, Model $record) => $record->invoices()->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('total'))
->summarize(Sum::make('total')
->label('')
->money()
->using(fn (Table $table, $query) => $query->tap(fn ($q) => self::applyDateFilters($q, $table))->sum('total'))
),
->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([
@ -113,16 +115,11 @@ public static function table(Table $table): Table
DatePicker::make('created_until')
->label('Until date'),
]),
], layout: Tables\Enums\FiltersLayout::AboveContent);
}
protected static function applyDateFilters($query, Table $table): void
{
$createdAt = $table->getFilter('created_at')?->getState()['created_at'] ?? null;
$createdUntil = $table->getFilter('created_until')?->getState()['created_until'] ?? null;
$query->when($createdAt, fn ($q, $date) => $q->whereDate('created_at', '>=', $date));
$query->when($createdUntil, fn ($q, $date) => $q->whereDate('created_at', '<=', $date));
])
->actions([
])
->bulkActions([
]);
}
public static function canAccess(): bool

View File

@ -8,7 +8,6 @@
use App\Filament\Admin\Resources\CustomerResource\RelationManagers\PaymentsRelationManager;
use App\Filament\Admin\Resources\CustomerResource\RelationManagers\ShippingEntriesRelationManager;
use App\Models\Customer;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
@ -30,44 +29,16 @@ class CustomerResource extends Resource
public static function form(Form $form): Form
{
return $form
->columns(1)
->schema([
Section::make([
Fieldset::make('Primary Information')
->columns(1)
->columnSpan(fn (?Customer $record) => $record ? 1 : 3)
->schema([
TextInput::make('company_name')
->required(),
TextInput::make('phone'),
]),
Fieldset::make('Shipping Address')
->columns(1)
->columnSpan(fn (?Customer $record) => $record ? 1 : 3)
->schema([
TextInput::make('shipping_address_line_1')
->label('Line 1')
->placeholder('618 East Kent Ave S #108'),
TextInput::make('shipping_address_line_2')
->label('Line 2')
->placeholder('Vancouver, BC V5X 0B2, Canada'),
]),
Fieldset::make('Billing Address')
->columns(1)
->columnSpan(fn (?Customer $record) => $record ? 1 : 3)
->schema([
TextInput::make('billing_address_line_1')
->label('Line 1')
->placeholder('618 East Kent Ave S #108'),
TextInput::make('billing_address_line_2')
->label('Line 2')
->placeholder('Vancouver, BC V5X 0B2, Canada'),
]),
])
->columns(3)
->columnSpan(fn (?Customer $record) => $record ? 1 : 3),
TextInput::make('company_name')
->required(),
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),
]);
}
@ -83,7 +54,7 @@ public static function table(Table $table): Table
TextColumn::make('balance')
->getStateUsing(fn (Customer $customer) => $customer->calculateBalance())
->money()
->hidden(! auth()->user()->is_admin ?? false),
->hidden(! auth()->user()->is_admin),
])
->filters([
//
@ -112,7 +83,8 @@ public static function getPages(): array
{
return [
'index' => \App\Filament\Admin\Resources\CustomerResource\Pages\ListCustomers::route('/'),
'edit' => \App\Filament\Admin\Resources\CustomerResource\Pages\EditCustomer::route('/{record}/edit'),
// 'create' => \App\Filament\Admin\Resources\CustomerResource\Pages\CreateCustomer::route('/create'),
'edit' => \App\Filament\Admin\Resources\CustomerResource\Pages\EditCustomer::route('/{record}/edit'),
];
}
}

View File

@ -2,7 +2,6 @@
namespace App\Filament\Admin\Resources\CustomerResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\CustomerResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
@ -14,11 +13,7 @@ class ListCustomers extends ListRecords
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->modal()
->modalWidth('lg')
->icon(IconEnum::NEW->value)
->successRedirectUrl(fn ($record) => CustomerResource::getUrl('edit', ['record' => $record->id])),
Actions\CreateAction::make(),
];
}
}

View File

@ -2,7 +2,6 @@
namespace App\Filament\Admin\Resources\CustomerResource\RelationManagers;
use App\Enums\IconEnum;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
@ -42,8 +41,7 @@ public function table(Table $table): Table
//
])
->headerActions([
Tables\Actions\CreateAction::make()
->icon(IconEnum::NEW->value),
Tables\Actions\CreateAction::make(),
])
->actions([
Tables\Actions\EditAction::make(),

View File

@ -6,8 +6,8 @@
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
class PaymentsRelationManager extends RelationManager
{
@ -27,10 +27,14 @@ public function form(Form $form): Form
public function table(Table $table): Table
{
return PaymentResource::table($table);
}
public static function canViewForRecord(Model $ownerRecord, string $pageClass): bool
{
return auth()->user()->is_admin ?? false;
/* return $table
->recordTitleAttribute('amount')
->columns([
Tables\Columns\TextColumn::make('amount'),
])
->headerActions([
Tables\Actions\CreateAction::make(),
]);*/
}
}

View File

@ -2,7 +2,6 @@
namespace App\Filament\Admin\Resources\CustomerResource\RelationManagers;
use App\Enums\IconEnum;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
@ -41,8 +40,7 @@ public function table(Table $table): Table
//
])
->headerActions([
Tables\Actions\CreateAction::make()
->icon(IconEnum::NEW->value),
Tables\Actions\CreateAction::make(),
])
->actions([
// Tables\Actions\EditAction::make(),

View File

@ -3,12 +3,8 @@
namespace App\Filament\Admin\Resources;
use App\Enums\IconEnum;
use App\Enums\InvoiceStatus;
use App\Filament\Admin\Resources\InvoiceReportResource\RelationManagers\InvoicesRelationManager;
use App\Models\InvoiceReport;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\ToggleButtons;
@ -34,47 +30,28 @@ public static function form(Form $form): Form
return $form
->schema([
Section::make([
Group::make([
Select::make('customer_id')
->relationship('customer', 'company_name')
->preload()
->required()
->columnSpanFull()
->searchable(),
ToggleButtons::make('payment_types')
->required()
->options(InvoiceStatus::class)
->multiple()
->columnSpanFull()
->inline(),
DatePicker::make('date_start')
->required()
->columnSpan(1),
DatePicker::make('date_end')
->required()
->default(today())
->columnSpan(1),
])->columnSpan(fn (?InvoiceReport $record) => $record === null ? 5 : 3)
->columns(2),
Select::make('customer_id')
->relationship('customer', 'company_name')
->preload()
->required()
->searchable(),
ToggleButtons::make('filter_paid')
->boolean()
->required()
->default(false)
->colors([
'true' => 'info',
'false' => 'info',
])
->inline(),
DatePicker::make('date_start')
->required(),
DatePicker::make('date_end')
->required()
->default(today()),
])
->columns(5)
->columnSpan(fn ($record) => $record === null ? 3 : 2),
Section::make([
Placeholder::make('created_at')
->label('Created')
->content(fn (InvoiceReport $record): ?string => $record->created_at?->diffForHumans()),
Placeholder::make('updated_at')
->label('Last modified')
->content(fn (InvoiceReport $record): ?string => $record->updated_at?->diffForHumans()),
])
->columnSpan(1)
->hidden(fn (?InvoiceReport $record) => $record === null)
->extraAttributes(['class' => 'h-full']),
->columns(2)
->columnSpan(3),
])->columns(3);
}
@ -103,6 +80,7 @@ public static function table(Table $table): Table
TextColumn::make('balance')
->weight(FontWeight::Bold)
->money(),
// ->getStateUsing(fn (Invoice))
])
->defaultSort('id', 'desc');
}
@ -122,9 +100,9 @@ public static function getRelations(): array
public static function getPages(): array
{
return [
'index' => \App\Filament\Admin\Resources\InvoiceReportResource\Pages\ListInvoiceReports::route('/'),
// 'create' => \App\Filament\Admin\Resources\InvoiceReportResource\Pages\CreateInvoiceReport::route('/create'),
'view' => \App\Filament\Admin\Resources\InvoiceReportResource\Pages\ViewInvoiceReport::route('/{record}'),
'index' => \App\Filament\Admin\Resources\InvoiceReportResource\Pages\ListInvoiceReports::route('/'),
'create' => \App\Filament\Admin\Resources\InvoiceReportResource\Pages\CreateInvoiceReport::route('/create'),
'view' => \App\Filament\Admin\Resources\InvoiceReportResource\Pages\ViewInvoiceReport::route('/{record}'),
];
}
}

View File

@ -2,6 +2,10 @@
namespace App\Filament\Admin\Resources\InvoiceReportResource\Pages;
use App\Filament\Admin\Resources\InvoiceReportResource;
use Filament\Resources\Pages\CreateRecord;
class CreateInvoiceReport extends CreateRecord {}
class CreateInvoiceReport extends CreateRecord
{
protected static string $resource = InvoiceReportResource::class;
}

View File

@ -2,8 +2,6 @@
namespace App\Filament\Admin\Resources\InvoiceReportResource\Pages;
use App\Enums\IconEnum;
use App\Enums\InvoiceStatus;
use App\Filament\Admin\Resources\InvoiceReportResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
@ -17,32 +15,7 @@ class ListInvoiceReports extends ListRecords
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->modalWidth('xl')
->icon(IconEnum::NEW->value)
->mutateFormDataUsing(function ($data) {
/* Initialize all payment statues to false,
map selected payment types to corresponding status,
assign filtered statuses to specific keys */
$paymentTypes = array_fill_keys(array_map(fn ($status) => $status->name, InvoiceStatus::cases()), false);
if (! empty($data['payment_types'])) {
foreach ($data['payment_types'] as $type) {
$statusName = InvoiceStatus::from($type)->name;
$paymentTypes[$statusName] = true;
}
}
foreach ($paymentTypes as $status => $value) {
$data['with_'.strtolower($status)] = $value;
}
unset($data['payment_types']);
return $data;
})
->successRedirectUrl(fn ($record) => InvoiceReportResource::getUrl('view', ['record' => $record->id])),
Actions\CreateAction::make(),
];
}
}

View File

@ -2,12 +2,10 @@
namespace App\Filament\Admin\Resources\InvoiceReportResource\Pages;
use App\Enums\InvoiceStatus;
use App\Filament\Admin\Resources\InvoiceReportResource;
use App\Models\InvoiceReport;
use Filament\Actions\Action;
use Filament\Resources\Pages\ViewRecord;
use Illuminate\Contracts\Support\Htmlable;
class ViewInvoiceReport extends ViewRecord
{
@ -15,24 +13,6 @@ class ViewInvoiceReport extends ViewRecord
protected static ?string $title = 'View Invoice Report';
public function getTitle(): string|Htmlable
{
return parent::getTitle().' '.$this->record->internal_id;
}
public function mutateFormDataBeforeFill(array $data): array
{
foreach (InvoiceStatus::cases() as $case) {
$name = 'with_'.strtolower($case->name);
if ($data[$name]) {
$data['payment_types'][] = $case->value ?? null;
}
}
return $data;
}
protected function getHeaderActions(): array
{
return [

View File

@ -35,37 +35,22 @@ public function table(Table $table): Table
->label('ID')
->extraHeaderAttributes(['class' => 'w-full'])
->color('primary'),
TextColumn::make('date')
->label('Created')
->date('Y-m-d'),
->date(),
TextColumn::make('subtotal')
->alignRight()
->money(),
TextColumn::make('gst_amount')
->label('GST/HST')
->getStateUsing(function (Invoice $record) {
return $record->has_gst
? '$'.number_format($record->gst_amount, 2)
: ($record->has_hst ? '$'.number_format($record->hst_amount, 2) : '-');
})
->label('GST')
->alignRight()
->money(),
TextColumn::make('pst_amount')
->label('PST')
->alignRight()
->formatStateUsing(function ($state) {
return $state == 0.00 ? '-' : '$'.$state;
}),
TextColumn::make('total')
->money()
->alignRight()
->weight(FontWeight::Medium),
TextColumn::make('balance')
->alignRight()
->getStateUsing(fn (Invoice $record) => $record->remainingBalance())

View File

@ -6,11 +6,9 @@
use App\Enums\InvoiceStatus;
use App\Filament\Admin\Resources\CustomerResource\RelationManagers\InvoicesRelationManager;
use App\Filament\Admin\Resources\InvoiceResource\RelationManagers\OrdersRelationManager;
use App\Filament\Admin\Resources\InvoiceResource\RelationManagers\PaymentsRelationManager;
use App\Filament\Admin\Resources\InvoiceResource\RelationManagers\ProductServicesRelationManager;
use App\Models\Customer;
use App\Models\Invoice;
use App\Models\Payment;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Group;
@ -18,14 +16,12 @@
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Split;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Support\Enums\FontWeight;
use Filament\Tables;
use Filament\Tables\Actions\BulkAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
@ -48,53 +44,66 @@ public static function form(Form $form): Form
Group::make()
->schema([
Section::make([
Group::make([
Select::make('customer_id')
->required()
->label('Customer')
->options(Customer::all()->pluck('company_name', 'id'))
->reactive()
->searchable()
->disabledOn('edit')
->columnSpan(2),
Select::make('customer_id')
Split::make([
DatePicker::make('date')
->required()
->label('Customer')
->options(Customer::all()->pluck('company_name', 'id'))
->reactive()
->searchable()
->disabledOn('edit')
->columnSpan(2),
->default(today()),
DatePicker::make('due_date'),
])
->columnSpan(2),
Split::make([
DatePicker::make('date')
->required()
->default(today()),
DatePicker::make('due_date'),
])->columnSpan(2),
Grid::make(3)
->schema([
ToggleButtons::make('has_gst')
->label('GST')
->boolean('On', 'Off')
->default(true)
// ->inline()
->colors([
'true' => 'info',
'false' => 'info',
]),
ToggleButtons::make('status')
->options(InvoiceStatus::class)
->required()
->inline()
->default(InvoiceStatus::UNPAID)
->columnSpan(2),
ToggleButtons::make('has_pst')
->label('PST')
->boolean('On', 'Off')
->default(false)
// ->inline()
->colors([
'true' => 'info',
'false' => 'info',
]),
Grid::make(3)
->schema([
Toggle::make('has_gst')
->label('GST')
->inline(false)
->default(true),
ToggleButtons::make('has_hst')
->label('HST')
->boolean('On', 'Off')
->default(false)
// ->inline()
->colors([
'true' => 'info',
'false' => 'info',
]),
])->columnSpan(1),
Toggle::make('has_pst')
->label('PST')
->inline(false)
->default(false),
ToggleButtons::make('status')
->options(InvoiceStatus::class)
->required()
->inline()
->default(InvoiceStatus::UNPAID)
->columnSpan(1),
Toggle::make('has_hst')
->label('HST')
->inline(false)
->default(false),
]),
])->columnSpan(fn (?Invoice $record) => $record === null ? 2 : 1),
])
->columns(2)
->columnSpan(fn (?Invoice $record) => $record === null ? 3 : 2),
->columnSpan(2),
Section::make()
->schema([
@ -150,60 +159,40 @@ public static function table(Table $table): Table
TextColumn::make('created_at')
->label('Created')
->date('Y-m-d')
->searchable()
->date()
->sortable(),
TextColumn::make('subtotal')
->money()
->alignRight()
->sortable()
->searchable(),
->alignRight(),
// FIXME: sortable doesn't sort correctly
TextColumn::make('gst_amount')
->label('GST/HST')
TextColumn::make('has_gst')
->label('GST')
->money()
->getStateUsing(function (Invoice $record) {
return $record->has_gst
? '$'.number_format($record->gst_amount, 2)
: ($record->has_hst ? '$'.number_format($record->hst_amount, 2) : '-');
})
->alignRight()
->searchable(query: function (Builder $query, string $search) {
$query->where(function ($query) use ($search) {
$query->where('hst_amount', 'like', "%{$search}%")
->orWhere('gst_amount', 'like', "%{$search}%");
});
}),
// ->sortable(query: function (Builder $query, string $direction) {
// $query->orderByRaw("COALESCE(hst_amount, gst_amount, 0) $direction");
// }),
->formatStateUsing(function (Invoice $record) {
if ($record->has_gst) {
return '$'.number_format($record->gst_amount, 2);
}
TextColumn::make('pst_amount')
return '-';
})
->alignRight(),
TextColumn::make('has_pst')
->label('PST')
->getStateUsing(function (Invoice $record) {
return $record->has_pst ? '$'.number_format($record->pst_amount, 2) : '-';
->formatStateUsing(function (Invoice $record) {
if ($record->has_pst) {
return '$'.number_format($record->pst_amount, 2);
}
return '-';
})
->alignRight()
->sortable()
->searchable(),
TextColumn::make('total')
->money()
->alignRight()
->weight(FontWeight::Medium)
->sortable()
->searchable(),
->alignRight(),
TextColumn::make('balance')
->getStateUsing(fn (Invoice $record) => $record->remainingBalance())
->searchable(query: fn (Builder $query, string $search) => $query->searchByBalance($search))
->label('Balance')
->money()
->alignRight()
->weight(FontWeight::Bold),
TextColumn::make('status')
->badge(InvoiceStatus::class)
->sortable(),
@ -246,48 +235,18 @@ public static function table(Table $table): Table
])
->bulkActions([
BulkAction::make('Create Payment')
->icon(IconEnum::PAYMENTS->value)
->form(fn ($form) => PaymentResource::form($form))
->action(function (Collection $records, array $data) {
if ($records->pluck('customer_id')->unique()->count() !== 1) {
Notification::make()
->title('Invalid order combination')
->body('Make sure all orders are from the same customer')
->danger()
->send();
return;
}
$payment = Payment::create([
'customer_id' => $records->pluck('customer_id')->first(),
'amount' => $data['amount'],
'date' => $data['date'],
'check_number' => $data['check_number'],
'notes' => $data['notes'],
]);
$payment->applyToInvoices($records);
Tables\Actions\BulkAction::make('Mark as paid')
->action(function (Collection $records) {
$records->each->setStatus(InvoiceStatus::PAID);
Notification::make()
->title('Payment created successfully')
->title(count($records).' item(s) saved successfully')
->success()
->send();
}),
})
->icon('lucide-circle-check')
->deselectRecordsAfterCompletion(),
Tables\Actions\BulkActionGroup::make([
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\BulkAction::make('Mark as unpaid')
->action(function (Collection $records) {
$records->each->setStatus(InvoiceStatus::UNPAID);
@ -315,16 +274,15 @@ public static function getRelations(): array
return [
OrdersRelationManager::class,
ProductServicesRelationManager::class,
PaymentsRelationManager::class,
];
}
public static function getPages(): array
{
return [
'index' => \App\Filament\Admin\Resources\InvoiceResource\Pages\ListInvoices::route('/'),
// 'create' => \App\Filament\Admin\Resources\InvoiceResource\Pages\CreateInvoice::route('/create'),
'edit' => \App\Filament\Admin\Resources\InvoiceResource\Pages\EditInvoice::route('/{record}/edit'),
'index' => \App\Filament\Admin\Resources\InvoiceResource\Pages\ListInvoices::route('/'),
'create' => \App\Filament\Admin\Resources\InvoiceResource\Pages\CreateInvoice::route('/create'),
'edit' => \App\Filament\Admin\Resources\InvoiceResource\Pages\EditInvoice::route('/{record}/edit'),
];
}
}

View File

@ -7,17 +7,11 @@
use Filament\Actions;
use Filament\Actions\Action;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Contracts\Support\Htmlable;
class EditInvoice extends EditRecord
{
protected static string $resource = InvoiceResource::class;
public function getTitle(): string|Htmlable
{
return parent::getTitle().' '.$this->record->internal_id;
}
protected function getHeaderActions(): array
{
return [

View File

@ -23,10 +23,6 @@ public function getTabs(): array
->query(fn ($query) => $query->where('status', InvoiceStatus::UNPAID))
->icon(InvoiceStatus::UNPAID->getIcon()),
'partially_paid' => Tab::make('Partially Paid')
->query(fn ($query) => $query->where('status', InvoiceStatus::PARTIALLY_PAID))
->icon(InvoiceStatus::PARTIALLY_PAID->getIcon()),
'paid' => Tab::make('Paid')
->query(fn ($query) => $query->where('status', InvoiceStatus::PAID))
->icon(InvoiceStatus::PAID->getIcon()),
@ -41,11 +37,7 @@ public function getTabs(): array
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->modal()
->modalWidth('lg')
->icon(IconEnum::NEW->value)
->successRedirectUrl(fn ($record) => InvoiceResource::getUrl('edit', ['record' => $record->id])),
Actions\CreateAction::make(),
];
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\InvoiceResource\RelationManagers;
use App\Filament\Admin\Resources\PaymentResource;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables\Table;
class PaymentsRelationManager extends RelationManager
{
protected static string $relationship = 'payments';
public function form(Form $form): Form
{
return PaymentResource::form($form);
}
public function table(Table $table): Table
{
return PaymentResource::paymentRelationManagerTable($table)
->recordTitleAttribute('date');
}
}

View File

@ -3,11 +3,10 @@
namespace App\Filament\Admin\Resources;
use App\Enums\IconEnum;
use App\Enums\InvoiceStatus;
use App\Enums\OrderAttributes;
use App\Enums\OrderStatus;
use App\Enums\OrderType;
use App\Models\Invoice;
use App\Models\Customer;
use App\Models\Order;
use App\Models\OrderProduct;
use App\Models\ProductService;
@ -29,8 +28,6 @@
use Filament\Resources\Resource;
use Filament\Support\Enums\MaxWidth;
use Filament\Tables;
use Filament\Tables\Actions\BulkAction;
use Filament\Tables\Actions\BulkActionGroup;
use Filament\Tables\Columns\IconColumn\IconColumnSize;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
@ -60,13 +57,11 @@ public static function form(Form $form): Form
->options(OrderType::class)
->searchable(),
// Split::make([
Select::make('customer_id')
->required()
->label('Customer')
->relationship(name: 'customer', titleAttribute: 'company_name')
->preload()
->createOptionForm(fn ($form) => CustomerResource::form($form))
->createOptionAction(fn ($action) => $action->modalWidth('lg'))
->options(Customer::all()->pluck('company_name', 'id'))
->searchable(),
TextInput::make('customer_po')
@ -186,7 +181,6 @@ public static function form(Form $form): Form
->defaultItems(1),
Repeater::make('services')
->view('filament.forms.compact-repeater')
->label('Product Services')
->schema([
Grid::make(19)
@ -208,13 +202,13 @@ public static function form(Form $form): Form
->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('placement')
->datalist(ProductService::all()->unique('placement')->pluck('placement')->toArray())
->columnSpan(3),
TextInput::make('serviceFileSetupNumber')
->label('Setup')
->columnSpan(1)
@ -349,6 +343,7 @@ public static function table(Table $table): Table
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkAction::make('updateStatus')
->form([
Select::make('status')
@ -371,95 +366,18 @@ public static function table(Table $table): Table
->color('info')
->deselectRecordsAfterCompletion(),
BulkActionGroup::make([
BulkAction::make('Create individual invoices')
->icon(IconEnum::INVOICE->value)
->action(function (Collection $records): void {
[$invoiced, $toInvoice] = $records->partition(fn ($record) => $record->invoice);
$toInvoice->each(function ($record) {
$invoice = Invoice::create([
'customer_id' => $record->customer->id,
'date' => today(),
'status' => InvoiceStatus::UNPAID->value,
]);
$invoice->orders()->save($record);
$invoice->calculateTotals();
$record->update(['status' => OrderStatus::INVOICED->value]);
});
if ($invoiced->isNotEmpty()) {
Notification::make()
->title("{$invoiced->count()} orders are already invoiced")
->warning()
->send();
}
if ($toInvoice->isNotEmpty()) {
Notification::make()
->title("Successfully created {$toInvoice->count()} invoice(s)")
->success()
->send();
}
}),
BulkAction::make('Add all to new invoice')
->icon(IconEnum::REPEAT->value)
->action(function (Collection $records): void {
if ($records->pluck('customer_id')->unique()->count() !== 1) {
Notification::make()
->title('Invalid order combination')
->body('Make sure all orders are from the same customer')
->danger()
->send();
return;
}
[$invoiced, $validOrders] = $records->partition(fn ($record) => $record->invoice);
if ($validOrders->isNotEmpty()) {
$invoice = Invoice::create([
'customer_id' => $records->first()->customer_id,
'date' => today(),
'status' => InvoiceStatus::UNPAID->value,
]);
$invoice->orders()->saveMany($validOrders);
$invoice->calculateTotals(); // FIXME: Investigate why this is needed.
Order::whereIn('id', $validOrders->pluck('id'))->update([
'status' => OrderStatus::INVOICED->value,
]);
}
if ($invoiced->isNotEmpty()) {
Notification::make()
->title('Some orders are already invoiced')
->body("{$invoiced->count()} orders are already invoiced and will not be added")
->warning()
->send();
}
if ($validOrders->isNotEmpty()) {
Notification::make()
->title('Invoice created')
->body("{$validOrders->count()} orders have been added to this invoice")
->success()
->send();
}
}),
])
->label('Invoicing')
->hidden(fn () => ! auth()->user()->is_admin),
BulkActionGroup::make([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
])->label('Other actions'),
]),
]);
}
public static function getRelations(): array
{
return [
];
}
public static function getPages(): array
{
return [

View File

@ -2,13 +2,9 @@
namespace App\Filament\Admin\Resources\OrderResource\Pages;
use App\Enums\IconEnum;
use App\Enums\InvoiceStatus;
use App\Enums\OrderAttributes;
use App\Enums\OrderStatus;
use App\Filament\Admin\Resources\InvoiceResource;
use App\Filament\Admin\Resources\OrderResource;
use App\Models\Invoice;
use App\Models\Order;
use App\Models\OrderProduct;
use App\Models\ProductService;
@ -17,20 +13,13 @@
use App\Models\ServiceType;
use Filament\Actions;
use Filament\Actions\Action;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Database\Eloquent\Model;
class EditOrder extends EditRecord
{
protected static string $resource = OrderResource::class;
public function getTitle(): string|Htmlable
{
return parent::getTitle().' '.$this->record->internal_po;
}
protected function mutateFormDataBeforeFill(array $data): array
{
$order = Order::findOrFail($data['id']);
@ -159,11 +148,12 @@ protected function getHeaderActions(): array
Action::make('save')
->label('Save changes')
->action('save')
->icon(IconEnum::SAVE->value),
->icon('lucide-save'),
Actions\ReplicateAction::make()
->label('Duplicate')
->icon(IconEnum::COPY->value)
->icon('lucide-copy')
->color('info')
->mutateRecordDataUsing(function (array $data): array {
$po = 'Duplicate of '.$data['customer_po'];
$data['customer_po'] = $po;
@ -205,37 +195,23 @@ protected function getHeaderActions(): array
})
->successRedirectUrl(fn (Model $replica): string => OrderResource::getUrl('edit', [$replica])),
Action::make('invoice')
->visible(fn () => auth()->user()->is_admin)
->label(fn (Order $record) => $record->invoice()->exists() ? 'To Invoice' : 'Make Invoice')
->icon(IconEnum::INVOICE->value)
->action(function (Order $record) {
if ($record->invoice()->exists()) {
return redirect()->to(InvoiceResource::getUrl('edit', ['record' => $record->invoice->id]));
}
$invoice = Invoice::create([
'customer_id' => $record->customer_id,
'date' => today(),
'status' => InvoiceStatus::UNPAID->value,
]);
$invoice->orders()->save($record);
return Notification::make()
->title('Invoice '.$invoice->internal_id.' created successfully')
->body('Click the button again to go to the invoice')
->success()
->send();
}),
// Action::make('invoice')
// ->visible(fn () => auth()->user()->is_admin)
// ->label('To Invoice')
// ->icon('lucide-receipt-text'),
//
Action::make('print')
->icon(IconEnum::PRINT->value)
->icon('lucide-printer')
->url(fn (Order $record) => route('orders.pdf', $record))
->openUrlInNewTab(),
Actions\DeleteAction::make()
->icon(IconEnum::TRASH->value),
->icon('lucide-trash-2'),
];
}
// protected function getRedirectUrl(): string
// {
// return $this->previousUrl ?? $this->getResource()::getUrl('index');
// }
}

View File

@ -15,69 +15,77 @@ class ListOrders extends ListRecords
{
protected static string $resource = OrderResource::class;
private function excludeStatuses($query): mixed
{
return $query
->whereNot('status', OrderStatus::DRAFT)
->whereNot('status', OrderStatus::READY_FOR_INVOICE)
->whereNot('status', OrderStatus::INVOICED)
->whereNot('status', OrderStatus::SHIPPED);
}
private function getBadgeCount(callable $queryCallback): ?int
{
$count = Order::query()->when(true, $queryCallback)
->whereNot('status', OrderStatus::DRAFT)
->whereNot('status', OrderStatus::READY_FOR_INVOICE)
->whereNot('status', OrderStatus::INVOICED)
->whereNot('status', OrderStatus::SHIPPED)
->count();
return $count > 0 ? $count : null;
}
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->icon(IconEnum::NEW->value),
Actions\CreateAction::make(),
];
}
public function getTabs(): array
{
return [
'all' => Tab::make('All')
->icon(IconEnum::TAB_ALL->value),
'active' => Tab::make()
->query(fn ($query) => $this->excludeStatuses($query))
->query(function ($query) {
return $query
->whereNot('status', OrderStatus::INVOICED)
->whereNot('status', ORderStatus::SHIPPED);
})
->icon(OrderStatus::PRODUCTION->getIcon())
->badge(fn () => $this->getBadgeCount(fn ($query) => $this->excludeStatuses($query))),
->badge(function () {
return Order::whereNot('status', OrderStatus::SHIPPED)
->whereNot('status', OrderStatus::INVOICED)
->count();
}),
'unprinted' => Tab::make()
->query(fn ($query) => $this->excludeStatuses($query)->where('printed', false))
->icon(IconEnum::PRINT->value)
->badge(fn () => $this->getBadgeCount(fn ($query) => $query->where('printed', false)))
->query(function ($query) {
return $query->where('printed', false);
})
->icon(IconEnum::TAB_UNPRINTED->value)
->badge(function () {
$count = Order::where('printed', false)->count();
return $count > 0 ? $count : null;
})
->badgeColor('success'),
'overdue' => Tab::make()
->query(fn ($query) => $this->excludeStatuses($query)->whereDate('due_date', '<=', today()))
->query(function ($query) {
return $query->whereDate('due_date', '<=', today())
->whereNot('status', OrderStatus::INVOICED)
->whereNot('status', ORderStatus::SHIPPED);
})
->icon(IconEnum::TAB_OVERDUE->value)
->badge(fn () => $this->getBadgeCount(fn ($query) => $query->whereDate('due_date', '<=', today())))
->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(fn ($query) => $this->excludeStatuses($query)->where('rush', true))
->query(function ($query) {
return $query->where('rush', true)
->whereNot('status', OrderStatus::INVOICED)
->whereNot('status', OrderStatus::SHIPPED);
})
->icon(OrderAttributes::rush->getIcon())
->badge(fn () => $this->getBadgeCount(fn ($query) => $query->where('rush', true)))
->badge(function () {
$count = Order::where('rush', true)
->whereNot('status', OrderStatus::INVOICED)
->whereNot('status', OrderStatus::SHIPPED)
->count();
return $count > 0 ? $count : null;
})
->badgeColor('warning'),
'ready_for_invoice' => Tab::make()
->query(fn ($query) => $query->where('status', OrderStatus::READY_FOR_INVOICE))
->icon(OrderStatus::READY_FOR_INVOICE->getIcon())
->badge(fn () => ($count = Order::query()->where('status', OrderStatus::READY_FOR_INVOICE)->count()) > 0 ? $count : null)
->badgeColor(OrderStatus::READY_FOR_INVOICE->getColor()),
'all' => Tab::make('All')
->icon(IconEnum::TAB_ALL->value),
];
}
}

View File

@ -2,7 +2,6 @@
namespace App\Filament\Admin\Resources\OrderResource\RelationManagers;
use App\Enums\IconEnum;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
@ -36,18 +35,15 @@ public function table(Table $table): Table
//
])
->headerActions([
Tables\Actions\CreateAction::make()
->icon(IconEnum::NEW->value),
Tables\Actions\CreateAction::make(),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make()
->icon(IconEnum::TRASH->value),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make()
->icon(IconEnum::TRASH->value),
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}

View File

@ -46,6 +46,7 @@ public static function form(Form $form): Form
->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')

View File

@ -2,7 +2,6 @@
namespace App\Filament\Admin\Resources\PackingSlipResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\PackingSlipResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
@ -14,8 +13,7 @@ class EditPackingSlip extends EditRecord
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make()
->icon(IconEnum::TRASH->value),
Actions\DeleteAction::make(),
];
}
}

View File

@ -2,7 +2,6 @@
namespace App\Filament\Admin\Resources\PackingSlipResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\PackingSlipResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
@ -14,8 +13,7 @@ class ListPackingSlips extends ListRecords
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->icon(IconEnum::NEW->value),
Actions\CreateAction::make(),
];
}
}

View File

@ -2,20 +2,16 @@
namespace App\Filament\Admin\Resources;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\InvoiceResource\Pages\ListInvoices;
use App\Filament\Admin\Resources\CustomerResource\RelationManagers\PaymentsRelationManager;
use App\Filament\Admin\Resources\PaymentResource\Pages;
use App\Filament\Admin\Resources\PaymentResource\RelationManagers\InvoicesRelationManager;
use App\Models\Payment;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;
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\Actions\ViewAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
@ -23,7 +19,7 @@ class PaymentResource extends Resource
{
protected static ?string $model = Payment::class;
protected static ?string $navigationIcon = IconEnum::PAYMENTS->value;
protected static ?string $navigationIcon = 'lucide-hand-coins';
protected static ?string $navigationGroup = 'Financial';
@ -34,108 +30,61 @@ public static function form(Form $form): Form
return $form
->schema([
Section::make([
Group::make([
Select::make('customer_id')
->relationship('customer', 'company_name')
->required()
->searchable()
->hidden(fn ($livewire) => $livewire::class === ListInvoices::class)
->preload()
->columnSpanFull(),
TextInput::make('amount')
->required()
->prefix('$')
->rules('numeric')
->minValue(0)
->maxValue(99999999)
->columnSpan(3),
TextInput::make('check_number')
->columnSpan(6),
DatePicker::make('date')
->default(today())
->columnSpan(4),
Placeholder::make('break_2')->columnSpan(3)->hiddenLabel(),
Textarea::make('notes')
->columnSpanFull(),
])->columnSpan(fn (?Payment $record) => $record === null ? 9 : 3)
->columns(9),
])->columns(9),
Select::make('customer_id')
->relationship('customer', 'company_name')
->required()
->searchable()
->preload(),
TextInput::make('amount')
->required()
->minValue(0)
->maxValue(99999999)
->numeric(),
Textarea::make('notes'),
]),
]);
}
public static function table(Table $table, ?bool $showSearchable = true): Table
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('created_at')
->label('Date')
->date('Y-m-d')
->searchable($showSearchable),
->searchable(),
TextColumn::make('customer.company_name')
->hidden(fn ($livewire) => $livewire::class !== Pages\ListPayments::class)
->searchable($showSearchable),
->hidden(fn ($livewire) => $livewire::class === PaymentsRelationManager::class)
->searchable(),
TextColumn::make('check_number')
->searchable($showSearchable)
TextColumn::make('notes')
->limit(100)
->extraHeaderAttributes(['class' => 'w-full']),
TextColumn::make('amount')
->searchable($showSearchable)
->alignRight()
->searchable()
->numeric()
->money(),
TextColumn::make('unapplied_amount')
->searchable($showSearchable)
->label('Balance')
->alignRight()
->money(),
])
->actions([
\Filament\Tables\Actions\EditAction::make(),
]);
}
public static function paymentRelationManagerTable(Table $table): Table
{
return $table
->columns([
TextColumn::make('id')
->color('primary'),
TextColumn::make('created_at')
->label('Date')
->date('Y-m-d'),
TextColumn::make('check_number')
->extraHeaderAttributes(['class' => 'w-full']),
TextColumn::make('amount')
->label('Total amount')
->alignRight()
->money(),
TextColumn::make('applied_amount')
->alignRight()
->money(),
ViewAction::make(),
]);
}
public static function canAccess(): bool
{
return auth()->user()->is_admin ?? false;
return auth()->user()->is_admin;
}
public static function getRelations(): array
{
return [
InvoicesRelationManager::class,
//
];
}
@ -143,7 +92,9 @@ public static function getPages(): array
{
return [
'index' => Pages\ListPayments::route('/'),
'edit' => Pages\EditPayment::route('/{record}/edit'),
// 'view' => Pages\ViewPayment::route('/{record}'),
// 'create' => Pages\CreatePayment::route('/create'),
// 'edit' => Pages\EditPayment::route('/{record}/edit'),
];
}
}

View File

@ -16,7 +16,7 @@ class ListPayments extends ListRecords
protected function getHeaderActions(): array
{
return [
/* Actions\Action::make('distributePayments')
Actions\Action::make('distributePayments')
->icon(IconEnum::DISTRIBUTE_PAYMENTS->value)
->action(function (PaymentService $paymentService) {
$paymentService->distributePayments();
@ -26,12 +26,9 @@ protected function getHeaderActions(): array
->body('Payments have been distributed')
->success()
->send();
}),*/
}),
Actions\CreateAction::make()
->modalWidth('lg')
->icon(IconEnum::NEW->value)
->successRedirectUrl(fn ($record) => PaymentResource::getUrl('edit', ['record' => $record->id])),
Actions\CreateAction::make(),
];
}
}

View File

@ -1,36 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\PaymentResource\RelationManagers;
use App\Filament\Admin\Resources\InvoiceResource;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class InvoicesRelationManager extends RelationManager
{
protected static string $relationship = 'invoices';
public function form(Form $form): Form
{
return $form
->schema([
TextInput::make('internal_id')
->required()
->maxLength(255),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('internal_id')
->columns([
TextColumn::make('internal_id')
->color('primary')
->url(fn ($record) => InvoiceResource::getUrl('edit', ['record' => $record->id])),
]);
}
}

View File

@ -7,7 +7,6 @@
use App\Models\Quote;
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;
@ -37,28 +36,24 @@ public static function form(Form $form): Form
Grid::make(3)
->schema([
Section::make([
Group::make([
Select::make('customer_id')
->required()
->label('Customer')
->options(Customer::all()->pluck('company_name', 'id'))
->reactive()
->searchable()
->columnSpan(1),
Select::make('customer_id')
->required()
->label('Customer')
->options(Customer::all()->pluck('company_name', 'id'))
->reactive()
->searchable()
->columnSpan(1),
DatePicker::make('date')
->default(today())
->required(),
DatePicker::make('date')
->default(today())
->required(),
TextArea::make('notes')
->rows(3)
->columnSpan(2),
]),
TextArea::make('notes')
->rows(3)
->columnSpan(2),
])
->columns(2)
->columnSpan(fn (?Quote $record) => $record === null ? 3 : 2)
->extraAttributes(['class' => 'h-full']),
->columnSpan(fn (?Quote $record) => $record === null ? 3 : 2),
Section::make()
->schema([
@ -74,8 +69,7 @@ public static function form(Form $form): Form
])
->columnSpan(1)
->hidden(fn (?Quote $record) => $record === null)
->extraAttributes(['class' => 'h-full']),
->hidden(fn (?Quote $record) => $record === null),
]),
TableRepeater::make('embroideryEntries')
@ -121,24 +115,30 @@ public static function form(Form $form): Form
TextInput::make('logo')
->label('Logo name')
->columnSpan(2),
TextInput::make('placement'),
TextInput::make('quantity')
->rules(['numeric'])
->label('Qty'),
->rules(['numeric']),
TextInput::make('width')
->rules('numeric'),
TextInput::make('height')
->rules('numeric'),
TextInput::make('setup_amount')
->label('Setup qty')
->rules('numeric'),
TextInput::make('color_amount')
->label('Color qty')
->rules('numeric'),
TextInput::make('color_match')
->rules('numeric'),
TextInput::make('color_change')
->rules('numeric'),
Select::make('color_match')
->required()
->options([
true => 'Yes',
false => 'No',
])
->default(false),
Select::make('color_change')
->required()
->options([
true => 'Yes',
false => 'No',
])
->default(false),
TextInput::make('flash')
->rules(['numeric']),
TextInput::make('fleece')
@ -147,33 +147,19 @@ public static function form(Form $form): Form
->rules('numeric'),
TextInput::make('run_charge')
->rules('numeric'),
TextInput::make('artwork_fee')
->label('Artwork fee')
->rules('numeric'),
TextInput::make('repacking_fee')
->label('Repack. fee')
TextInput::make('other_charges')
->rules('numeric'),
])
->addActionLabel('Add Screen Print Entry')
->defaultItems(0)
->reorderable()
->colStyles([
'logo' => 'width: 11%',
'placement' => 'width: 11%',
'quantity' => 'width: 5%',
'width' => 'width: 6%',
'height' => 'width: 6%',
'setup_amount' => 'width: 5%',
'color_amount' => 'width: 5%',
'color_match' => 'width: 6%',
'color_change' => 'width: 5%',
'flash' => 'width: 6%',
'fleece' => 'width: 6%',
'poly_ink' => 'width: 6%',
'run_charge' => 'width: 6%',
'artwork_fee' => 'width: 6%',
'repacking_fee' => 'width: 6%',
'logo' => 'width: 15%',
'quantity' => 'width: 5%',
'width' => 'width: 6%',
'height' => 'width: 6%',
'setup_amount' => 'width: 5%',
'color_amount' => 'width: 5%',
]),
TableRepeater::make('heatTransferEntries')
@ -181,7 +167,6 @@ public static function form(Form $form): Form
->schema([
TextInput::make('logo')
->label('Logo name'),
TextInput::make('placement'),
TextInput::make('quantity')
->prefix('#')
->rules('numeric'),
@ -199,12 +184,11 @@ public static function form(Form $form): Form
->defaultItems(0)
->reorderable()
->colStyles([
'logo' => 'width: 25%',
'placement' => 'width: 25%',
'quantity' => 'width: 10%',
'width' => 'width: 11%',
'height' => 'width: 11%',
'price' => 'width: 15%',
'logo' => 'width: 20%',
'quantity' => 'width: 10%',
'width' => 'width: 11%',
'height' => 'width: 11%',
'price' => 'width: 15%',
]),
])->columns(1);
@ -218,8 +202,6 @@ public static function table(Table $table): Table
->color('primary')
->searchable(),
TextColumn::make('internal_id'),
TextColumn::make('date')
->date('Y-m-d')
->sortable()

View File

@ -2,7 +2,6 @@
namespace App\Filament\Admin\Resources\QuoteResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\QuoteResource;
use App\Models\Quote;
use Filament\Actions;
@ -13,26 +12,20 @@ class EditQuote extends EditRecord
{
protected static string $resource = QuoteResource::class;
public function getTitle(): string|\Illuminate\Contracts\Support\Htmlable
{
return parent::getTitle().' '.$this->record->getKey();
}
protected function getHeaderActions(): array
{
return [
Action::make('save')
->label('Save changes')
->action('save')
->icon(IconEnum::SAVE->value),
->icon('lucide-save'),
Action::make('print')
->icon(IconEnum::PRINT->value)
->icon('lucide-printer')
->url(fn (Quote $record) => route('pdf.quote', $record))
->openUrlInNewTab(),
Actions\DeleteAction::make()
->icon(IconEnum::TRASH->value),
Actions\DeleteAction::make(),
];
}
}

View File

@ -2,7 +2,6 @@
namespace App\Filament\Admin\Resources\QuoteResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\QuoteResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
@ -14,8 +13,7 @@ class ListQuotes extends ListRecords
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->icon(IconEnum::NEW->value),
Actions\CreateAction::make(),
];
}
}

View File

@ -9,8 +9,6 @@
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Columns\Summarizers\Sum;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
@ -44,67 +42,44 @@ public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('name')
Tables\Columns\TextColumn::make('name')
->label('Code'),
TextColumn::make('value')
Tables\Columns\TextColumn::make('value')
->label('Long Name')
->extraHeaderAttributes(['class' => 'w-full']),
TextColumn::make('productServices.amount')
->label('Quantity')
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']
);
})
->summarize(Sum::make('amount')
->label('')
->using(fn (Table $table, $query) => $query
->when($createdAt = $table->getFilter('created_at')->getState()['created_at'] ?? null,
fn ($q) => $q->whereDate('created_at', '>=', $createdAt))
->when($createdUntil = $table->getFilter('created_until')->getState()['created_until'] ?? null,
fn ($q) => $q->whereDate('created_at', '<=', $createdUntil))
->sum('amount')
)
),
}),
TextColumn::make('productServices.amount_price')
->label('Amount')
Tables\Columns\TextColumn::make('amount')
->alignRight()
->money()
->getStateUsing(function (Table $table, Model $record) {
return $record->getAmountAttribute(
$table->getFilter('created_at')->getState()['created_at'],
$table->getFilter('created_until')->getState()['created_until']
);
})
->summarize(Sum::make('amount_price')
->label('')
->money()
->using(fn (Table $table, $query) => $query
->when($createdAt = $table->getFilter('created_at')->getState()['created_at'] ?? null,
fn ($q) => $q->whereDate('created_at', '>=', $createdAt))
->when($createdUntil = $table->getFilter('created_until')->getState()['created_until'] ?? null,
fn ($q) => $q->whereDate('created_at', '<=', $createdUntil))
->sum('amount_price')
)
),
->money('usd'),
TextColumn::make('salesPercentage')
Tables\Columns\TextColumn::make('salesPercentage')
->alignRight()
->suffix('%')
->label('% sales')
->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'),
TextColumn::make('averagePrice')
Tables\Columns\TextColumn::make('averagePrice')
->getStateUsing(function (Table $table, Model $record) {
return $record->getAveragePriceAttribute(
$table->getFilter('created_at')->getState()['created_at'],

View File

@ -2,7 +2,6 @@
namespace App\Filament\Admin\Resources\ShippingEntryResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\ShippingEntryResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
@ -14,8 +13,7 @@ class ListShippingEntries extends ListRecords
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->icon(IconEnum::NEW->value),
Actions\CreateAction::make(),
];
}
}

View File

@ -5,6 +5,7 @@
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\TaxRateResource\Pages;
use App\Models\TaxRate;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
@ -35,8 +36,24 @@ public static function form(Form $form): Form
->label('Value in percentage')
->numeric()
->prefix('%'),
]),
]);
])
->columns(1)
->columnSpan(2),
Section::make()
->schema([
Placeholder::make('created_at')
->label('Created')
->content(fn (TaxRate $record): ?string => $record->created_at?->diffForHumans()),
Placeholder::make('updated_at')
->label('Last modified')
->content(fn (TaxRate $record): ?string => $record->updated_at?->diffForHumans()),
])
->columnSpan(1)
->hidden(fn (?TaxRate $record) => $record === null)
->extraAttributes(['class' => 'h-full']),
])->columns(3);
}
public static function table(Table $table): Table
@ -53,8 +70,7 @@ public static function table(Table $table): Table
//
])
->actions([
Tables\Actions\EditAction::make()
->modalWidth('xs'),
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([

View File

@ -2,7 +2,6 @@
namespace App\Filament\Admin\Resources\TaxRateResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\TaxRateResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
@ -14,10 +13,7 @@ class ListTaxRates extends ListRecords
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->modalWidth('xs')
->icon(IconEnum::NEW->value)
->createAnother(false),
Actions\CreateAction::make(),
];
}
}

View File

@ -4,6 +4,7 @@
use App\Enums\IconEnum;
use App\Models\User;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
@ -35,29 +36,32 @@ public static function form(Form $form): Form
->autocomplete('new-username')
->unique()
->required()
->columnSpanFull(),
->columnSpan(1),
TextInput::make('password')
->password()
->autocomplete('new-password')
->revealable()
->dehydrated(fn ($state) => ! empty($state))
->required(fn (string $operation): bool => $operation === 'create'),
Grid::make(2)
->schema([
TextInput::make('password')
->password()
->autocomplete('new-password')
->revealable()
->dehydrated(fn ($state) => ! empty($state))
->required(fn (string $operation): bool => $operation === 'create'),
TextInput::make('password_verify')
->label('Verify password')
->password()
->revealable()
->same('password')
->dehydrated(false)
->required(fn (string $operation) => $operation === 'create'),
TextInput::make('password_verify')
->label('Verify password')
->password()
->revealable()
->same('password')
->dehydrated(false)
->required(fn (string $operation) => $operation === 'create'),
])
->columnSpan(2),
]),
Section::make('Permissions')
->description('Administrators can access invoices and settings')
->description('Administrators can access financial information and change settings.')
->schema([
Toggle::make('is_admin')
->columnSpanFull()
->label('User is an administrator')
->reactive()
->afterStateUpdated(fn ($state, callable $set) => $set('customer_id', null))
@ -66,7 +70,7 @@ public static function form(Form $form): Form
->columns(2),
Section::make('Customer Login')
->description('If this account is for a customer, select them here')
->description('If this account is for a customer, select them here.')
->schema([
Select::make('customer_id')
@ -98,9 +102,7 @@ public static function table(Table $table): Table
//
])
->actions([
Tables\Actions\EditAction::make()
->modalWidth('md')
->modal(),
Tables\Actions\EditAction::make()->modal(),
])
->defaultSort('customer_id', 'asc');
}

View File

@ -2,7 +2,6 @@
namespace App\Filament\Admin\Resources\UserResource\Pages;
use App\Enums\IconEnum;
use App\Filament\Admin\Resources\UserResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
@ -15,8 +14,6 @@ protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->modalWidth('md')
->icon(IconEnum::NEW->value)
->modal(),
];
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Filament\Admin\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);
}
}

View File

@ -35,9 +35,7 @@ protected function getStats(): array
private function getActiveOrders(): string
{
return Order::all()
->where('order_status', '!=', OrderStatus::DRAFT)
->where('order_status', '!=', OrderStatus::SHIPPED)
->where('order_status', '!=', OrderStatus::READY_FOR_INVOICE)
->where('order_status', '!=', OrderStatus::INVOICED)
->count();
}
@ -45,8 +43,6 @@ private function getActiveOrders(): string
private function getOrdersPast30Days(): string
{
return Order::all()
->where('order_status', '!=', OrderStatus::DRAFT)
->where('order_status', '!=', OrderStatus::READY_FOR_INVOICE)
->where('order_status', '!=', OrderStatus::SHIPPED)
->where('order_status', '!=', OrderStatus::INVOICED)
->whereBetween('created_at', [now()->startOfMonth(), now()->endOfMonth()])
@ -69,8 +65,6 @@ private function getOrdersInPast30DaysChart(): array
private function getDueOrders(): string
{
return Order::all()
->where('order_status', '!=', OrderStatus::DRAFT)
->where('order_status', '!=', OrderStatus::READY_FOR_INVOICE)
->where('order_status', '!=', OrderStatus::SHIPPED)
->where('order_status', '!=', OrderStatus::INVOICED)
->where('due_date', '<=', now())

View File

@ -0,0 +1,33 @@
<?php
namespace App\Filament\Admin\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);
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Livewire;
use App\Enums\OrderStatus;
use App\Enums\OrderType;
use App\Models\Customer;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Carbon;
use Livewire\Component;
class CreateOrder extends Component
{
public Collection $customers;
public string $selectedCustomer;
public $contacts;
public function mount(Collection $customers): void
{
$this->customers = $customers;
$this->contacts = $customers->first()->contacts;
}
public function getContacts(): void
{
$this->contacts = Customer::find($this->selectedCustomer)->contacts;
}
public function render(): View
{
return view('livewire.create-order', [
'contacts' => $this->contacts,
'order_types' => OrderType::cases(),
'order_status' => OrderStatus::cases(),
'customers' => $this->customers,
'today' => Carbon::today()->format('Y-m-d'),
'due_default' => Carbon::today()->addDay(10)->format('Y-m-d'),
]);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Livewire;
use App\Models\Customer;
use Illuminate\Support\Collection;
use Livewire\Component;
class CustomerAndContactSelect extends Component
{
public Collection $customers;
public Collection $contacts;
public string $selectedCustomer;
public function mount(Collection $customers)
{
$this->customers = $customers;
if (isset($this->selectedCustomer)) {
$this->contacts = Customer::find($this->selectedCustomer)->contacts;
} else {
$this->contacts = $customers->first()->contacts;
}
}
public function updateContactList()
{
$this->contacts = Customer::find($this->selectedCustomer)->contacts;
}
public function render()
{
return view('livewire.customer-and-contact-select', [
'customers' => $this->customers,
'contacts' => $this->contacts,
]);
}
}

View File

@ -0,0 +1,172 @@
<?php
namespace App\Livewire;
use Exception;
use Illuminate\Contracts\View\Factory;
use Illuminate\Foundation\Application;
use Illuminate\Support\Collection;
use Illuminate\View\View;
use Livewire\Component;
class OrderProductsCreate extends Component
{
/** @var Collection<string, int> */
public Collection $productInputs;
/** @var Collection<string, int> */
public Collection $serviceInputs;
/** @var array<int> */
public array $sizes = [];
/** @var array<int> */
public array $totals = [];
/** @var array<int> */
public array $units = [];
/**
* @var array<int>
*/
public array $prices = [];
/**
* @var array<int>
*/
public array $priceTotals = [];
public int $totalQuantity = 0;
public string $totalPrice = '$0.00';
public function updated(): void
{
try {
foreach ($this->sizes as $index => $size) {
$this->totals[$index] = array_sum($size);
}
} catch (Exception $e) {
}
try {
foreach ($this->units as $index => $unit) {
$this->priceTotals[$index] = $unit * $this->prices[$index];
}
} catch (Exception $e) {
}
$this->totalQuantity = array_sum($this->totals);
$this->totalPrice = '$'.number_format(round(array_sum($this->priceTotals), 2), 2);
}
public function addProductInput(): void
{
$index = $this->productInputs->count();
$this->productInputs->push([
$index => [
'sku' => '',
'product_name' => '',
'product_color' => '',
'size_xs' => '',
'size_s' => '',
'size_m' => '',
'size_l' => '',
'size_xl' => '',
'size_2xl' => '',
'size_3xl' => '',
'size_osfa' => '',
'product_total' => '',
],
]);
}
public function determineAddProductRow(int $index): void
{
if ($index == $this->productInputs->count() - 1) {
$this->addProductInput();
}
}
public function determineAddServiceProductRow(int $index): void
{
if ($index == $this->serviceInputs->count() - 1) {
$this->addServiceInput();
}
}
public function removeProductInput(int $key): void
{
if ($this->productInputs->count() > 1) {
$this->productInputs->pull($key);
}
}
public function addServiceInput(): void
{
$this->serviceInputs->push([
$this->serviceInputs->count() => [
'service_name' => '',
'product_name' => '',
'product_color' => '',
'logo_name' => '',
'setup_number' => '',
'service_width' => '',
'service_height' => '',
'service_setup_unit' => '',
'service_setup_price' => '',
'service_total' => '',
],
]);
}
public function removeServiceInput(int $key): void
{
if ($this->serviceInputs->count() > 1) {
$this->serviceInputs->pull($key);
}
}
public function mount(): void
{
$this->fill([
'productInputs' => collect([
[
'sku' => '',
'product_name' => '',
'product_color' => '',
'size_xs' => '',
'size_s' => '',
'size_m' => '',
'size_l' => '',
'size_xl' => '',
'size_2xl' => '',
'size_3xl' => '',
'size_osfa' => '',
'product_total' => '0',
],
]),
'serviceInputs' => collect([
[
'sku' => '',
'product_name' => '',
'product_color' => '',
'logo_name' => '',
'setup_number' => '',
'service_width' => '',
'service_height' => '',
'service_setup_unit' => '',
'service_setup_price' => '',
'service_total' => '',
],
]),
]);
}
public function render(): \Illuminate\Contracts\View\View|Factory|Application|View
{
return view('livewire.order-products-create');
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace App\Livewire;
use App\Models\Order;
use Illuminate\Contracts\View\Factory;
use Illuminate\Foundation\Application;
use Illuminate\Support\Carbon;
use Illuminate\View\View;
use Livewire\Component;
use Livewire\WithPagination;
class OrdersTable extends Component
{
use WithPagination;
protected string $paginationTheme = 'bootstrap';
public bool $showCustomerColumn;
public string $orderType = 'active';
public string $search = '';
public string $title = '';
public string $customer_id = '';
public Carbon $today;
public function mount(bool $showCustomerColumn, string $orderType, string $title, ?string $customer_id = null): void
{
$this->today = Carbon::today();
$this->showCustomerColumn = $showCustomerColumn;
$this->orderType = $orderType;
$this->title = $title;
$this->customer_id = $customer_id ?? '';
}
public function render(): \Illuminate\Contracts\View\View|Factory|Application|View
{
return view('livewire.orders-table', [
'orders' => Order::with('customer')
->when($this->customer_id != null, fn ($q) => $q->where('customer_id', $this->customer_id))
->when($this->orderType === 'active', fn ($q) => $q->active())
->when($this->orderType === 'invoiced', fn ($q) => $q->invoiced())
->when($this->orderType === 'finished', fn ($q) => $q->finished())
->when($this->search !== '', function ($query) {
$query->whereHas('customer', function ($query) {
$query->where('company_name', 'like', '%'.$this->search.'%');
})->orWhere('customer_po', 'like', '%'.$this->search.'%')
->orWhere('internal_po', 'like', '%'.$this->search.'%')
->orWhere('order_date', 'like', '%'.$this->search.'%')
->orWhere('due_date', 'like', '%'.$this->search.'%')
->orWhere('status', 'like', '%'.$this->search.'%');
})
->orderByDesc('rush')
->orderBy('due_date')
->paginate(15)
->withQueryString(),
'today' => $this->today,
]);
}
}

View File

@ -12,7 +12,6 @@ class HeatTransferEntry extends Model
protected $fillable = [
'quote_id',
'placement',
'quantity',
'logo',
'width',

View File

@ -5,7 +5,6 @@
use App\Enums\InvoiceStatus;
use App\Observers\InvoiceObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -56,19 +55,6 @@ class Invoice extends Model
'total' => 'float',
];
public function scopeSearchByBalance(Builder $query, $amount): Builder
{
if (! is_numeric($amount)) {
return $query;
}
return $query->whereRaw('total - (SELECT IFNULL(SUM(applied_amount), 0)
FROM payments
INNER JOIN invoice_payment
ON payments.id = invoice_payment.payment_id
WHERE invoice_payment.invoice_id = invoices.id) = ?', [$amount]);
}
public function remainingBalance(): float
{
$applied = $this->payments()->sum('applied_amount');

View File

@ -17,11 +17,8 @@ class InvoiceReport extends Model
'customer_id',
'date_start',
'date_end',
'filter_paid',
'subtotal',
'with_unpaid',
'with_partially_paid',
'with_paid',
'with_void',
'pst',
'gst',
];
@ -41,26 +38,25 @@ public static function boot(): void
parent::boot();
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->with_unpaid, fn ($query) => $query->whereNot('status', InvoiceStatus::UNPAID))
->when(! $model->with_partially_paid, fn ($query) => $query->whereNot('status', InvoiceStatus::PARTIALLY_PAID))
->when(! $model->with_paid, fn ($query) => $query->whereNot('status', InvoiceStatus::PAID))
->when(! $model->with_void, fn ($query) => $query->whereNot('status', InvoiceStatus::VOID));
->when($model->filter_paid, function ($query) {
$query->where('status', InvoiceStatus::UNPAID);
});
$model->invoices()->sync($invoices->pluck('id')->toArray());
// $model->total = $model->invoices()->where('status', 'UNPAID')->sum('total');
// Finally, save
$model->save();
});
}
public function combinedGstHstSum(): float
{
return $this->invoices()->sum('gst_amount') + $this->invoices()->sum('hst_amount');
}
public function getTotalAttribute()
{
return $this->invoices()->sum('total');

View File

@ -9,7 +9,6 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
#[ObservedBy(PaymentObserver::class)]
@ -19,23 +18,21 @@ class Payment extends Model
protected $fillable = [
'customer_id',
'check_number',
'date',
'amount',
'unapplied_amount',
'notes',
];
public function applyToInvoices(Collection $invoices): void
public function applyToInvoices(): void
{
$remaining = $this->unapplied_amount ?? $this->amount;
$filteredInvoices = $invoices->whereIn('status', [
InvoiceStatus::UNPAID,
InvoiceStatus::PARTIALLY_PAID,
]);
$invoices = Invoice::where('customer_id', $this->customer_id)
->where('status', InvoiceStatus::UNPAID)
->orderBy('date')
->get();
foreach ($filteredInvoices as $invoice) {
foreach ($invoices as $invoice) {
$balance = $invoice->remainingBalance();
if ($remaining <= 0) {
@ -50,7 +47,7 @@ public function applyToInvoices(Collection $invoices): void
if ($invoice->remainingBalance() == 0) {
$invoice->setStatus(InvoiceStatus::PAID);
} elseif ($applied > 0) {
$invoice->setStatus(InvoiceStatus::PARTIALLY_PAID);
$invoice->setStatus(InvoiceStatus::UNPAID);
}
}

View File

@ -2,21 +2,16 @@
namespace App\Models;
use App\Observers\QuoteObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
#[ObservedBy(QuoteObserver::class)]
class Quote extends Model
{
use HasFactory;
protected $fillable = [
'internal_id',
'customer_id',
'date',
'notes',
@ -35,11 +30,15 @@ public function getTotalAttribute(): float
$embDigitizingTotal = $this->embroideryEntries()->sum('digitizing_cost');
$embRunChargeTotal = $this->embroideryEntries()->sum('run_charge');
$scpTotal = $this->screenPrintEntries->sum(fn (ScreenPrintEntry $record) => $record->total_price);
$scpRunChargeTotal = $this->screenPrintEntries()->sum('run_charge');
$scpOtherChargeTotal = $this->screenPrintEntries()->sum('other_charges');
$scpFleeceTotal = $this->screenPrintEntries()->sum('fleece');
$scpFlashTotal = $this->screenPrintEntries()->sum('flash');
$scpPolyInkTotal = $this->screenPrintEntries()->sum('poly_ink');
$heatTransferTotal = $this->heatTransferEntries()->sum('price');
return $embDigitizingTotal + $embRunChargeTotal + $scpTotal + $heatTransferTotal;
return $embDigitizingTotal + $embRunChargeTotal + $scpRunChargeTotal + $scpOtherChargeTotal + $scpFleeceTotal + $scpFlashTotal + $scpPolyInkTotal + $heatTransferTotal;
}
public function customer(): BelongsTo

View File

@ -14,7 +14,6 @@ class ScreenPrintEntry extends Model
'quote_id',
'quantity',
'logo',
'placement',
'width',
'height',
'color_amount',
@ -25,8 +24,7 @@ class ScreenPrintEntry extends Model
'flash',
'fleece',
'poly_ink',
'artwork_fee',
'repacking_fee',
'other_charges',
'notes',
];
@ -36,9 +34,7 @@ class ScreenPrintEntry extends Model
protected function getTotalPriceAttribute(): float
{
$perUnitTotals = ($this->flash + $this->fleece + $this->poly_ink + $this->run_charge + $this->repacking_fee) * $this->quantity ?? 0;
return $perUnitTotals + $this->artwork_fee + $this->color_change + $this->color_match;
return $this->flash + $this->fleece + $this->poly_ink + $this->run_charge + $this->other_charges;
}
public function quote(): BelongsTo

View File

@ -42,7 +42,7 @@ public function getSalesPercentageAttribute($created_at = null, $created_until =
{
$query = ProductService::query()
->when($created_at, fn ($query) => $query->whereDate('created_at', '>=', $created_at))
->when($created_until, fn ($query) => $query->whereDate('created_at', '<=', $created_until));
->when($created_until, fn ($query) => $query->whereDate('created_until', '<=', $created_until));
$total = $query->count();

View File

@ -3,15 +3,13 @@
namespace App\Models;
use Database\Factories\UserFactory;
use Filament\Models\Contracts\FilamentUser;
use Filament\Models\Contracts\HasName;
use Filament\Panel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable implements FilamentUser, HasName
class User extends Authenticatable implements HasName
{
/** @use HasFactory<UserFactory> */
use HasFactory, Notifiable;
@ -50,11 +48,6 @@ protected function casts(): array
];
}
public function canAccessPanel(Panel $panel): bool
{
return true;
}
public function setIsAdminAttribute(bool $value): void
{
$this->attributes['is_admin'] = $value;

View File

@ -23,8 +23,7 @@ public function creating(Invoice $invoice): void
public function created(Invoice $invoice): void
{
// $invoice->internal_id = 'TN4'.str_pad($invoice->id, 4, '0', STR_PAD_LEFT);
$invoice->internal_id = 'TN'.$invoice->id + 4000;
$invoice->internal_id = 'INV4'.str_pad($invoice->id, 4, '0', STR_PAD_LEFT);
$invoice->saveQuietly();
$invoice->calculateTotals();

View File

@ -12,7 +12,7 @@ class OrderObserver
*/
public function created(Order $order): void
{
if ($order->invoice != null) {
if ($order->invoice()->exists()) {
$order->invoice->calculateTotals();
}
}
@ -22,7 +22,7 @@ public function created(Order $order): void
*/
public function updated(Order $order): void
{
if ($order->invoice != null) {
if ($order->invoice()->exists()) {
$order->invoice->calculateTotals();
}
}
@ -33,9 +33,7 @@ public function updated(Order $order): void
public function saved(Order $order): void
{
if ($order->isDirty(['invoice_id']) && Invoice::where('id', $order->invoice_id)->exists()) {
if ($order->invoice != null) {
$order->invoice->calculateTotals();
}
$order->invoice->calculateTotals();
}
}
@ -44,7 +42,7 @@ public function saved(Order $order): void
*/
public function deleted(Order $order): void
{
if ($order->invoice != null) {
if ($order->invoice()->exists()) {
$order->invoice->calculateTotals();
}
}

View File

@ -11,7 +11,7 @@ class PaymentObserver
*/
public function saved(Payment $payment): void
{
// $payment->applyToInvoices();
$payment->applyToInvoices();
}
/**

View File

@ -1,17 +0,0 @@
<?php
namespace App\Observers;
use App\Models\Quote;
class QuoteObserver
{
public function created(Quote $quote): void
{
$company_string = strtoupper(substr($quote->customer->company_name, 0, 3));
$padded_id = str_pad($quote->id, 4, '0', STR_PAD_LEFT);
$quote->internal_id = 'Q'.$padded_id.'-'.$company_string;
$quote->save();
}
}

View File

@ -2,6 +2,7 @@
namespace App\Providers;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@ -17,5 +18,8 @@ public function register(): void
/**
* Bootstrap any application services.
*/
public function boot(): void {}
public function boot(): void
{
Paginator::useBootstrapFive();
}
}

View File

@ -29,10 +29,9 @@ public function panel(Panel $panel): Panel
->path('admin')
->login(UsernameLogin::class)
->colors([
'primary' => Color::Blue,
'code' => Color::hex('#d63384'),
'invoicing' => Color::hex('#DD00DD'),
'invoiced' => Color::hex('#900090'),
'primary' => Color::Blue,
'code' => Color::hex('#d63384'),
'invoiced' => Color::hex('#900090'),
])
->discoverResources(in: app_path('Filament/Admin/Resources/'), for: 'App\\Filament\\Admin\\Resources')
->discoverPages(in: app_path('Filament/Admin/Pages'), for: 'App\\Filament\\Admin\\Pages')

View File

@ -14,11 +14,11 @@
"laravel/tinker": "^2.9",
"livewire/livewire": "^3.5",
"mallardduck/blade-lucide-icons": "^1.23",
"spatie/laravel-pdf": "^1.5",
"fakerphp/faker": "^1.23"
"spatie/laravel-pdf": "^1.5"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^3.5",
"fakerphp/faker": "^1.23",
"larastan/larastan": "^2.0",
"laravel/pint": "^1.17",
"laravel/sail": "^1.26",

1117
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -15,18 +15,6 @@
'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Version
|--------------------------------------------------------------------------
|
| This value is the version of your application. This value is used when
| the framework needs to place the application's version in a notification
| or any other location as required by the application or its packages.
*/
'version' => '20250311',
/*
|--------------------------------------------------------------------------
| Application Environment

View File

@ -17,12 +17,11 @@ class HeatTransferEntryFactory extends Factory
public function definition(): array
{
return [
'quantity' => $this->faker->numberBetween(1, 10),
'placement' => $this->faker->words(2, true),
'logo' => $this->faker->words(2, true),
'width' => $this->faker->randomFloat(2, 1, 5),
'height' => $this->faker->randomFloat(2, 1, 5),
'price' => $this->faker->randomFloat(2, 1, 10),
'quantity' => $this->faker->numberBetween(1, 10),
'logo' => $this->faker->words(2, true),
'width' => $this->faker->randomFloat(2, 1, 5),
'height' => $this->faker->randomFloat(2, 1, 5),
'price' => $this->faker->randomFloat(2, 1, 10),
];
}
}

View File

@ -14,15 +14,12 @@ class InvoiceReportFactory extends Factory
public function definition(): array
{
return [
'customer_id' => Customer::all()->shuffle()->first()->id,
'date_start' => Carbon::now()->subYear(),
'date_end' => Carbon::now(),
'with_unpaid' => $this->faker->boolean(40),
'with_partially_paid' => $this->faker->boolean(40),
'with_paid' => $this->faker->boolean(40),
'with_void' => $this->faker->boolean(40),
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
'customer_id' => Customer::all()->shuffle()->first()->id,
'date_start' => Carbon::now()->subYear(),
'date_end' => Carbon::now(),
'filter_paid' => $this->faker->boolean(40),
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
];
}
}

View File

@ -19,19 +19,17 @@ public function definition(): array
return [
'quantity' => random_int(1, 10),
'logo' => $this->faker->words(2, true),
'placement' => $this->faker->words(2, true),
'width' => $this->faker->randomFloat(2, 1, 5),
'height' => $this->faker->randomFloat(2, 1, 5),
'color_amount' => random_int(1, 5),
'setup_amount' => random_int(1, 5),
'artwork_fee' => $this->faker->randomFloat(2, 1, 10),
'color_change' => $this->faker->randomFLoat(2, 1, 10),
'color_match' => $this->faker->randomFLoat(2, 1, 10),
'flash' => $this->faker->randomFloat(2, 0, 2),
'fleece' => $this->faker->randomFloat(2, 0, 2),
'poly_ink' => $this->faker->randomFloat(2, 0, 2),
'run_charge' => $this->faker->randomFloat(2, 0, 2),
'repacking_fee' => $this->faker->randomFloat(2, 0, 2),
'run_charge' => $this->faker->randomFloat(2, 1, 10),
'color_change' => $this->faker->boolean(),
'color_match' => $this->faker->boolean(),
'flash' => $this->faker->randomFloat(2, 1, 10),
'fleece' => $this->faker->randomFloat(2, 1, 10),
'poly_ink' => $this->faker->randomFloat(2, 1, 10),
'other_charges' => $this->faker->randomFloat(2, 1, 10),
];
}
}

View File

@ -17,8 +17,7 @@ class TaxRateFactory extends Factory
public function definition(): array
{
return [
'name' => strtoupper($this->faker->randomLetter()).'ST',
'value' => random_int(1, 15),
//
];
}
}

View File

@ -15,7 +15,6 @@ public function up(): void
$table->id();
$table->foreignId('customer_id')->constrained();
$table->string('internal_id')->nullable();
$table->date('date');
$table->longText('notes')->nullable();

View File

@ -11,16 +11,16 @@ public function up(): void
Schema::create('invoice_reports', function (Blueprint $table) {
$table->id();
$table->string('internal_id')->nullable();
$table->foreignId('customer_id')->constrained();
$table->date('date_start');
$table->date('date_end');
$table->boolean('with_unpaid')->default(false);
$table->boolean('with_partially_paid')->default(false);
$table->boolean('with_paid')->default(false);
$table->boolean('with_void')->default(false);
$table->boolean('filter_paid');
$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->timestamps();
});

View File

@ -15,8 +15,6 @@ public function up(): void
$table->id();
$table->foreignId('customer_id')->constrained();
$table->text('check_number')->nullable();
$table->date('date')->default(today());
$table->decimal('amount', 8, 2);
$table->decimal('unapplied_amount', 8, 2)->nullable();
$table->text('notes')->nullable();

View File

@ -18,19 +18,17 @@ public function up(): void
$table->integer('quantity')->nullable();
$table->string('logo')->nullable();
$table->string('placement')->nullable();
$table->decimal('width', 6, 2)->nullable();
$table->decimal('height', 6, 2)->nullable();
$table->integer('color_amount')->nullable();
$table->integer('setup_amount')->nullable();
$table->decimal('run_charge', 8, 2)->nullable();
$table->decimal('color_change', 8, 2)->nullable();
$table->decimal('color_match', 8, 2)->nullable();
$table->decimal('flash', 8, 2)->nullable();
$table->decimal('fleece', 8, 2)->nullable();
$table->decimal('poly_ink', 8, 2)->nullable();
$table->decimal('artwork_fee', 8, 2)->nullable();
$table->decimal('repacking_fee', 8, 2)->nullable();
$table->boolean('color_change')->default(false);
$table->boolean('color_match')->default(false);
$table->decimal('flash', 8, 2)->default(false);
$table->decimal('fleece', 8, 2)->default(false);
$table->decimal('poly_ink', 8, 2)->default(false);
$table->decimal('other_charges', 8, 2)->default(false);
$table->text('notes')->nullable();
$table->timestamps();

View File

@ -17,7 +17,6 @@ public function up(): void
$table->foreignId('quote_id')->constrained()->cascadeOnDelete();
$table->integer('quantity')->nullable();
$table->string('placement')->nullable();
$table->string('logo')->nullable();
$table->decimal('width', 6, 2)->nullable();
$table->decimal('height', 6, 2)->nullable();

View File

@ -2,6 +2,7 @@
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
@ -33,5 +34,9 @@ public function run(): void
InvoiceReportSeeder::class,
]);
User::factory()->create([
'username' => 'admin',
'is_admin' => true,
]);
}
}

View File

@ -1,22 +0,0 @@
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
class DefaultUserSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
User::factory()->create([
'username' => 'admin',
'password' => \Hash::make('TopNotch13579!'),
'is_admin' => true,
]);
}
}

View File

@ -13,11 +13,6 @@ class UserSeeder extends Seeder
*/
public function run(): void
{
User::factory()->create([
'username' => 'admin',
'is_admin' => true,
]);
foreach (Customer::all() as $customer) {
User::factory([
'username' => str_replace(',', '', strtolower(explode(' ', $customer->company_name)[0])),

View File

@ -1,91 +0,0 @@
# deploy/Dockerfile
# stage 1: build stage
FROM php:8.3-fpm-alpine as build
# installing system dependencies and php extensions
RUN apk add --no-cache \
zip \
libzip-dev \
freetype \
libjpeg-turbo \
libpng \
freetype-dev \
libjpeg-turbo-dev \
libpng-dev \
nodejs \
npm \
icu-dev \
&& docker-php-ext-configure intl \
&& docker-php-ext-install intl \
&& docker-php-ext-configure zip \
&& docker-php-ext-install zip pdo pdo_mysql \
&& docker-php-ext-configure gd --with-freetype=/usr/include/ --with-jpeg=/usr/include/ \
&& docker-php-ext-install -j$(nproc) gd \
&& docker-php-ext-enable gd
# install composer
COPY --from=composer:2.7.6 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
# copy necessary files and change permissions
COPY . .
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 775 /var/www/html/storage \
&& chmod -R 775 /var/www/html/bootstrap/cache
# install php and node.js dependencies
RUN composer install --no-dev --prefer-dist \
&& npm install \
&& npm run build
RUN chown -R www-data:www-data /var/www/html/vendor \
&& chmod -R 775 /var/www/html/vendor
# stage 2: production stage
FROM php:8.3-fpm-alpine
# install nginx
RUN apk add --no-cache \
zip \
libzip-dev \
freetype \
libjpeg-turbo \
libpng \
freetype-dev \
libjpeg-turbo-dev \
libpng-dev \
oniguruma-dev \
gettext-dev \
freetype-dev \
nginx \
icu-dev \
&& docker-php-ext-configure zip intl \
&& docker-php-ext-install zip pdo pdo_mysql intl \
&& docker-php-ext-enable intl \
&& docker-php-ext-configure gd --with-freetype=/usr/include/ --with-jpeg=/usr/include/ \
&& docker-php-ext-install -j$(nproc) gd \
&& docker-php-ext-enable gd \
&& docker-php-ext-install bcmath \
&& docker-php-ext-enable bcmath \
&& docker-php-ext-install exif \
&& docker-php-ext-enable exif \
&& docker-php-ext-install gettext \
&& docker-php-ext-enable gettext \
&& docker-php-ext-install opcache \
&& docker-php-ext-enable opcache \
&& rm -rf /var/cache/apk/*
# copy files from the build stage
COPY --from=build /var/www/html /var/www/html
COPY ./deploy/nginx.conf /etc/nginx/http.d/default.conf
COPY ./deploy/php.ini "$PHP_INI_DIR/conf.d/app.ini"
WORKDIR /var/www/html
# add all folders where files are being stored that require persistence. if needed, otherwise remove this line.
VOLUME ["/var/www/html/storage/app"]
CMD ["sh", "-c", "nginx && php-fpm"]

View File

@ -1,63 +0,0 @@
# deploy/docker-compose.yml
version: '3.8'
services:
laravel:
restart: unless-stopped
container_name: sewtopnotch.com
build:
context: ../
dockerfile: ./deploy/Dockerfile
# allocate as many volumes as necessary, if needed.
volumes:
- ../storage/app:/var/www/html/storage/app
environment:
APP_NAME: ${APP_NAME}
APP_ENV: ${APP_ENV}
APP_DEBUG: ${APP_DEBUG}
APP_KEY: ${APP_KEY}
APP_VERSION: ${APP_VERSION}
APP_URL: ${APP_URL}
DB_CONNECTION: mysql
DB_HOST: database
DB_PORT: 3306
DB_DATABASE: ${DB_DATABASE}
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
MAIL_MAILER: ${MAIL_MAILER}
MAIL_HOST: ${MAIL_HOST}
MAIL_PORT: ${MAIL_PORT}
MAIL_USERNAME: ${MAIL_USERNAME}
MAIL_PASSWORD: ${MAIL_PASSWORD}
MAIL_ENCRYPTION: ${MAIL_ENCRYPTION}
MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS}
MAIL_FROM_NAME: ${MAIL_FROM_NAME}
ports:
- "8080:80"
networks:
- n-laravel
depends_on:
- database
database:
restart: unless-stopped
image: mariadb:lts-jammy
volumes:
- v-database:/var/lib/mysql
environment:
MARIADB_DATABASE: ${DB_DATABASE}
MARIADB_USER: ${DB_USERNAME}
MARIADB_PASSWORD: ${DB_PASSWORD}
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
networks:
- n-laravel
volumes:
v-database:
networks:
n-laravel:
driver: bridge

View File

@ -1,35 +0,0 @@
# deploy/nginx.conf
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html/public;
client_max_body_size 10M;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico {
access_log off; log_not_found off;
}
location = /robots.txt {
access_log off; log_not_found off;
}
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}

View File

View File

@ -0,0 +1,131 @@
// Use DBML to define your database structure
// Docs: https://dbml.dbdiagram.io/docs
// Table follows {
// following_user_id integer
// followed_user_id integer
// created_at timestamp
// }
Table users {
id uuid [primary key]
username varchar
role enum [note: 'admin, employee, customer']
created_at timestamp
}
Table orders {
id uuid [primary key]
fk_customer_id uuid
product_order varchar
order_date datetime
due_date datetime
status enum [note: 'waiting, approved, processing, waiting to ship, shipped']
rush bool
event bool
new_art bool
repeat bool
digitizing bool
purchased_garments bool
supplied_file bool
// code varchar
notes varchar
created_at timestamp
updated_at timestamp
deleted_at timestamp
}
Ref: orders.fk_customer_id > customers.id
Table order_types {
id uuid [primary key]
name varchar
value varchar [note: 'DTG, Embroidery, Screen Printing, Vinyl']
}
Table orders_order_types {
id uuid [primary key]
fk_order_id uuid
fk_order_type_id uuid
}
Ref: orders_order_types.fk_order_id > orders.id
Ref: orders_order_types.fk_order_type_id > order_types.id
Table order_products {
id uuid [pk]
fk_order_id uuid
sku varchar
product_name varchar
color varchar
}
Ref: order_products.fk_order_id > orders.id
Table product_sizes {
id uuid [pk]
fk_order_product_id uuid
size varchar
amount integer
}
Ref: product_sizes.fk_order_product_id > order_products.id
Table product_services {
id uuid [primary key]
index int
fk_order_product_id uuid
service varchar
file varchar
placement varchar
logo_name varchar
logo_width decimal
logo_height decimal
setup_amount integer
amount integer
amount_price decimal
}
Ref: product_services.fk_order_product_id > order_products.id
Table logos {
id uuid [primary key]
name varchar
width decimal
height decimal
}
Table customers {
id uuid [primary key]
fk_user_id uuid
company_name varchar
internal_name varchar [note: 'image group quadreal for example']
shipping_address varchar
billing_address varchar
created_at timestamp
updated_at timestamp
deleted_at timestamp
}
Ref: customers.fk_user_id > users.id
Table contacts {
id uuid [primary key]
fk_customer_id uuid
first_name varchar
last_name varchar
email varchar
phone varchar
notes varchar
}
Ref: contacts.fk_customer_id > customers.id
Table invoices {
id uuid [primary key]
fk_order_id uuid
}
Ref: invoices.fk_order_id > orders.id

1
gesture-evolution Submodule

@ -0,0 +1 @@
Subproject commit 65fe7885813f3d4dc87f0fc52495247592fe276c

1
mprocs.log Normal file
View File

@ -0,0 +1 @@
ERROR [mprocs::error] Error: channel closed

5
mprocs.yaml Normal file
View File

@ -0,0 +1,5 @@
procs:
sail:
shell: "/home/nisse/Code/topnotch_website/vendor/bin/sail up"
npm:
shell: "npm run dev"

597
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,11 +13,10 @@
"laravel-vite-plugin": "^1.0",
"postcss": "^8.4.47",
"sass": "^1.56.1",
"tailwindcss": "^3.4.17",
"tailwindcss": "^3.4.13",
"vite": "^5.0"
},
"dependencies": {
"@tailwindcss/vite": "^4.0.6",
"bootstrap-icons": "^1.11.3",
"puppeteer": "^23.8.0"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,21 @@
{
"node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff": {
"file": "assets/bootstrap-icons-BOrJxbIo.woff",
"src": "node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff"
},
"node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff2": {
"file": "assets/bootstrap-icons-BtvjY1KL.woff2",
"src": "node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff2"
},
"resources/js/app.js": {
"file": "assets/app-L09tcoah.js",
"name": "app",
"src": "resources/js/app.js",
"isEntry": true
},
"resources/sass/app.scss": {
"file": "assets/app-Dgcz2HWl.css",
"src": "resources/sass/app.scss",
"isEntry": true
}
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More