Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
43eebd9528 | |||
ea4f46f96d | |||
3defbd8253 | |||
77b9c558af | |||
6814821100 | |||
47669851ff | |||
0385f614d6 | |||
ef5acd3d06 | |||
8a34981742 | |||
|
f3e7819908 | ||
44ec2068c3 | |||
195722c570 | |||
e6fc32c369 | |||
aa340852e7 | |||
|
1192ca681b | ||
d025570d8d | |||
a75296f997 | |||
2a831578a9 | |||
6433bb8485 | |||
435224ff7c | |||
a4e4c30118 | |||
8f8b3a9943 | |||
076dcccbb8 | |||
899377d594 | |||
629b3276b5 | |||
ec6ae88888 | |||
dddbbb8f9b | |||
779d46d708 | |||
6fc753fc19 | |||
4955780f67 | |||
cab2b2c478 | |||
e45041566a | |||
16e17de6fb | |||
4fbb62353a | |||
6b5a758dbe | |||
686cda21bf | |||
aa60cf18ee | |||
2b377f72aa | |||
5bec1fc3d8 | |||
20dd032b40 | |||
0d1501362c | |||
09cc6b249e | |||
f0f10a4907 |
25
.dockerignore
Normal file
25
.dockerignore
Normal file
@ -0,0 +1,25 @@
|
||||
# .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
|
27
.gitea/workflows/deploy.yaml
Normal file
27
.gitea/workflows/deploy.yaml
Normal file
@ -0,0 +1,27 @@
|
||||
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
1
.gitignore
vendored
@ -108,3 +108,4 @@ fabric.properties
|
||||
.directory
|
||||
public
|
||||
_ide_helper.php
|
||||
public/build
|
||||
|
32
.scripts/deploy.sh
Normal file
32
.scripts/deploy.sh
Normal file
@ -0,0 +1,32 @@
|
||||
#!/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!"
|
76
README.md
76
README.md
@ -1,70 +1,8 @@
|
||||
https://github.com/spatie/laravel-pdf/discussions/90
|
||||
# Changelog
|
||||
|
||||
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).
|
||||
**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
|
||||
|
@ -4,20 +4,59 @@
|
||||
|
||||
enum IconEnum: string
|
||||
{
|
||||
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';
|
||||
// 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 DISTRIBUTE_PAYMENTS = 'lucide-rotate-cw';
|
||||
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';
|
||||
|
||||
// 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';
|
||||
}
|
||||
|
@ -8,30 +8,38 @@
|
||||
|
||||
enum InvoiceStatus: string implements HasColor, HasIcon, HasLabel
|
||||
{
|
||||
case UNPAID = 'Not paid';
|
||||
case PAID = 'Paid';
|
||||
case VOID = 'Void';
|
||||
case UNPAID = 'not_paid';
|
||||
case PARTIALLY_PAID = 'partially_paid';
|
||||
case PAID = 'paid';
|
||||
case VOID = 'void';
|
||||
|
||||
public function getLabel(): ?string
|
||||
public function getLabel(): string
|
||||
{
|
||||
return $this->value;
|
||||
return match ($this) {
|
||||
self::UNPAID => 'Not paid',
|
||||
self::PARTIALLY_PAID => 'Partially paid',
|
||||
self::PAID => 'Paid',
|
||||
self::VOID => 'Void',
|
||||
};
|
||||
}
|
||||
|
||||
public function getColor(): string|array|null
|
||||
{
|
||||
return match ($this) {
|
||||
self::UNPAID => 'warning',
|
||||
self::PAID => 'success',
|
||||
self::VOID => 'gray'
|
||||
self::UNPAID => 'danger',
|
||||
self::PARTIALLY_PAID => 'warning',
|
||||
self::PAID => 'success',
|
||||
self::VOID => 'gray'
|
||||
};
|
||||
}
|
||||
|
||||
public function getIcon(): ?string
|
||||
{
|
||||
return match ($this) {
|
||||
self::UNPAID => 'lucide-circle-x',
|
||||
self::PAID => 'lucide-circle-check',
|
||||
self::VOID => 'lucide-circle-slash',
|
||||
self::UNPAID => IconEnum::UNPAID->value,
|
||||
self::PARTIALLY_PAID => IconEnum::PARTIALLY_PAID->value,
|
||||
self::PAID => IconEnum::PAID->value,
|
||||
self::VOID => IconEnum::VOID->value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -23,13 +23,13 @@ public function getLabel(): ?string
|
||||
public function getIcon(): ?string
|
||||
{
|
||||
return match ($this) {
|
||||
self::new_art => 'lucide-brush',
|
||||
self::repeat => 'lucide-files',
|
||||
self::rush => 'lucide-bell-ring',
|
||||
self::event => 'lucide-calendar-range',
|
||||
self::digitizing => 'lucide-computer',
|
||||
self::garments => 'lucide-shirt',
|
||||
self::supplied_file => 'lucide-file-check',
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -8,36 +8,46 @@
|
||||
|
||||
enum OrderStatus: string implements HasColor, HasIcon, HasLabel
|
||||
{
|
||||
case DRAFT = 'Draft';
|
||||
case APPROVED = 'Approved';
|
||||
case PRODUCTION = 'Production';
|
||||
case SHIPPED = 'Shipped';
|
||||
case INVOICED = 'Invoiced';
|
||||
case DRAFT = 'draft';
|
||||
case APPROVED = 'approved';
|
||||
case PRODUCTION = 'production';
|
||||
case SHIPPED = 'shipped';
|
||||
case READY_FOR_INVOICE = 'ready_for_invoice';
|
||||
case INVOICED = 'invoiced';
|
||||
|
||||
public function getLabel(): ?string
|
||||
public function getLabel(): string
|
||||
{
|
||||
return $this->value;
|
||||
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',
|
||||
};
|
||||
}
|
||||
|
||||
public function getColor(): string|array|null
|
||||
{
|
||||
return match ($this) {
|
||||
self::DRAFT => 'gray',
|
||||
self::APPROVED => 'success',
|
||||
self::PRODUCTION => 'primary',
|
||||
self::SHIPPED => 'warning',
|
||||
self::INVOICED => 'invoiced',
|
||||
self::DRAFT => 'gray',
|
||||
self::APPROVED => 'success',
|
||||
self::PRODUCTION => 'primary',
|
||||
self::SHIPPED => 'warning',
|
||||
self::READY_FOR_INVOICE => 'invoicing',
|
||||
self::INVOICED => 'invoiced',
|
||||
};
|
||||
}
|
||||
|
||||
public function getIcon(): ?string
|
||||
{
|
||||
return match ($this) {
|
||||
self::DRAFT => 'lucide-pencil',
|
||||
self::APPROVED => 'lucide-check-check',
|
||||
self::PRODUCTION => 'lucide-refresh-cw',
|
||||
self::SHIPPED => 'lucide-send',
|
||||
self::INVOICED => 'lucide-credit-card',
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -6,14 +6,20 @@
|
||||
|
||||
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 $this->value;
|
||||
return match ($this) {
|
||||
self::EMB => 'Embroidery',
|
||||
self::SCP => 'Screen printing',
|
||||
self::DTG => 'Direct-to-garment',
|
||||
self::VINYL => 'Vinyl',
|
||||
self::MISC => 'Misc',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -20,10 +20,10 @@ public function getLabel(): ?string
|
||||
public function getIcon(): ?string
|
||||
{
|
||||
return match ($this) {
|
||||
self::THEY_SHIP => 'lucide-truck',
|
||||
self::WE_SHIP => 'lucide-house',
|
||||
self::PICKUP => 'lucide-handshake',
|
||||
self::OTHER => 'lucide-ellipsis'
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\ContactResource\Pages;
|
||||
|
||||
use App\Enums\IconEnum;
|
||||
use App\Filament\Resources\ContactResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
@ -13,7 +14,8 @@ class ListContacts extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
Actions\CreateAction::make()
|
||||
->icon(IconEnum::NEW->value),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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\Summarizer;
|
||||
use Filament\Tables\Columns\Summarizers\Sum;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
|
||||
class CustomerReportResource extends Resource
|
||||
{
|
||||
@ -41,68 +41,66 @@ 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('subtotal')
|
||||
Tables\Columns\TextColumn::make('invoices.subtotal')
|
||||
->label('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(function (Table $table, Model $record) {
|
||||
return $record->getSubtotalAttribute(
|
||||
$table->getFilter('created_at')->getState()['created_at'],
|
||||
$table->getFilter('created_until')->getState()['created_until']
|
||||
);
|
||||
}),
|
||||
->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'))
|
||||
),
|
||||
|
||||
Tables\Columns\TextColumn::make('gst')
|
||||
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')
|
||||
->label('GST')
|
||||
->money()
|
||||
->alignRight()
|
||||
->getStateUsing(function (Table $table, Model $record) {
|
||||
return $record->getGstAttribute(
|
||||
$table->getFilter('created_at')->getState()['created_at'],
|
||||
$table->getFilter('created_until')->getState()['created_until']
|
||||
);
|
||||
}),
|
||||
->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'))
|
||||
),
|
||||
|
||||
Tables\Columns\TextColumn::make('pst')
|
||||
Tables\Columns\TextColumn::make('invoices.pst_amount')
|
||||
->label('PST')
|
||||
->money()
|
||||
->alignRight()
|
||||
->getStateUsing(function (Table $table, Customer $record) {
|
||||
return $record->getPstAttribute(
|
||||
$table->getFilter('created_at')->getState()['created_at'],
|
||||
$table->getFilter('created_until')->getState()['created_until']
|
||||
);
|
||||
}),
|
||||
->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'))
|
||||
),
|
||||
|
||||
Tables\Columns\TextColumn::make('total')
|
||||
Tables\Columns\TextColumn::make('invoices.total')
|
||||
->label('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(function (Table $table, Model $record) {
|
||||
return $record->getTotalAttribute(
|
||||
$table->getFilter('created_at')->getState()['created_at'],
|
||||
$table->getFilter('created_until')->getState()['created_until']
|
||||
);
|
||||
}),
|
||||
->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'))
|
||||
),
|
||||
])
|
||||
|
||||
->filters([
|
||||
Tables\Filters\Filter::make('created_at')
|
||||
->form([
|
||||
@ -115,11 +113,16 @@ public static function table(Table $table): Table
|
||||
DatePicker::make('created_until')
|
||||
->label('Until date'),
|
||||
]),
|
||||
])
|
||||
->actions([
|
||||
])
|
||||
->bulkActions([
|
||||
]);
|
||||
], 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));
|
||||
}
|
||||
|
||||
public static function canAccess(): bool
|
||||
|
@ -8,6 +8,7 @@
|
||||
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;
|
||||
@ -29,16 +30,44 @@ class CustomerResource extends Resource
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->columns(1)
|
||||
->schema([
|
||||
Section::make([
|
||||
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),
|
||||
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),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -54,7 +83,7 @@ public static function table(Table $table): Table
|
||||
TextColumn::make('balance')
|
||||
->getStateUsing(fn (Customer $customer) => $customer->calculateBalance())
|
||||
->money()
|
||||
->hidden(! auth()->user()->is_admin),
|
||||
->hidden(! auth()->user()->is_admin ?? false),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
@ -83,8 +112,7 @@ public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => \App\Filament\Admin\Resources\CustomerResource\Pages\ListCustomers::route('/'),
|
||||
// 'create' => \App\Filament\Admin\Resources\CustomerResource\Pages\CreateCustomer::route('/create'),
|
||||
'edit' => \App\Filament\Admin\Resources\CustomerResource\Pages\EditCustomer::route('/{record}/edit'),
|
||||
'edit' => \App\Filament\Admin\Resources\CustomerResource\Pages\EditCustomer::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
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;
|
||||
@ -13,7 +14,11 @@ class ListCustomers extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
Actions\CreateAction::make()
|
||||
->modal()
|
||||
->modalWidth('lg')
|
||||
->icon(IconEnum::NEW->value)
|
||||
->successRedirectUrl(fn ($record) => CustomerResource::getUrl('edit', ['record' => $record->id])),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
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;
|
||||
@ -41,7 +42,8 @@ public function table(Table $table): Table
|
||||
//
|
||||
])
|
||||
->headerActions([
|
||||
Tables\Actions\CreateAction::make(),
|
||||
Tables\Actions\CreateAction::make()
|
||||
->icon(IconEnum::NEW->value),
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
|
@ -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,14 +27,10 @@ public function form(Form $form): Form
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return PaymentResource::table($table);
|
||||
}
|
||||
|
||||
/* return $table
|
||||
->recordTitleAttribute('amount')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('amount'),
|
||||
])
|
||||
->headerActions([
|
||||
Tables\Actions\CreateAction::make(),
|
||||
]);*/
|
||||
public static function canViewForRecord(Model $ownerRecord, string $pageClass): bool
|
||||
{
|
||||
return auth()->user()->is_admin ?? false;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\CustomerResource\RelationManagers;
|
||||
|
||||
use App\Enums\IconEnum;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
@ -40,7 +41,8 @@ public function table(Table $table): Table
|
||||
//
|
||||
])
|
||||
->headerActions([
|
||||
Tables\Actions\CreateAction::make(),
|
||||
Tables\Actions\CreateAction::make()
|
||||
->icon(IconEnum::NEW->value),
|
||||
])
|
||||
->actions([
|
||||
// Tables\Actions\EditAction::make(),
|
||||
|
@ -3,8 +3,12 @@
|
||||
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;
|
||||
@ -30,28 +34,47 @@ public static function form(Form $form): Form
|
||||
return $form
|
||||
->schema([
|
||||
Section::make([
|
||||
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()),
|
||||
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),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpan(3),
|
||||
->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(3);
|
||||
}
|
||||
|
||||
@ -80,7 +103,6 @@ public static function table(Table $table): Table
|
||||
TextColumn::make('balance')
|
||||
->weight(FontWeight::Bold)
|
||||
->money(),
|
||||
// ->getStateUsing(fn (Invoice))
|
||||
])
|
||||
->defaultSort('id', 'desc');
|
||||
}
|
||||
@ -100,9 +122,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}'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,6 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\InvoiceReportResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\InvoiceReportResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateInvoiceReport extends CreateRecord
|
||||
{
|
||||
protected static string $resource = InvoiceReportResource::class;
|
||||
}
|
||||
class CreateInvoiceReport extends CreateRecord {}
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
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;
|
||||
@ -15,7 +17,32 @@ class ListInvoiceReports extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
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])),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,12 @@
|
||||
|
||||
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
|
||||
{
|
||||
@ -13,6 +15,24 @@ 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 [
|
||||
|
@ -35,22 +35,37 @@ public function table(Table $table): Table
|
||||
->label('ID')
|
||||
->extraHeaderAttributes(['class' => 'w-full'])
|
||||
->color('primary'),
|
||||
|
||||
TextColumn::make('date')
|
||||
->label('Created')
|
||||
->date(),
|
||||
->date('Y-m-d'),
|
||||
|
||||
TextColumn::make('subtotal')
|
||||
->alignRight()
|
||||
->money(),
|
||||
|
||||
TextColumn::make('gst_amount')
|
||||
->label('GST')
|
||||
->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) : '-');
|
||||
})
|
||||
->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())
|
||||
|
@ -6,9 +6,11 @@
|
||||
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;
|
||||
@ -16,12 +18,14 @@
|
||||
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;
|
||||
@ -44,66 +48,53 @@ public static function form(Form $form): Form
|
||||
Group::make()
|
||||
->schema([
|
||||
Section::make([
|
||||
Select::make('customer_id')
|
||||
->required()
|
||||
->label('Customer')
|
||||
->options(Customer::all()->pluck('company_name', 'id'))
|
||||
->reactive()
|
||||
->searchable()
|
||||
->disabledOn('edit')
|
||||
->columnSpan(2),
|
||||
Group::make([
|
||||
|
||||
Split::make([
|
||||
DatePicker::make('date')
|
||||
Select::make('customer_id')
|
||||
->required()
|
||||
->default(today()),
|
||||
DatePicker::make('due_date'),
|
||||
])
|
||||
->columnSpan(2),
|
||||
->label('Customer')
|
||||
->options(Customer::all()->pluck('company_name', 'id'))
|
||||
->reactive()
|
||||
->searchable()
|
||||
->disabledOn('edit')
|
||||
->columnSpan(2),
|
||||
|
||||
Grid::make(3)
|
||||
->schema([
|
||||
ToggleButtons::make('has_gst')
|
||||
->label('GST')
|
||||
->boolean('On', 'Off')
|
||||
->default(true)
|
||||
// ->inline()
|
||||
->colors([
|
||||
'true' => 'info',
|
||||
'false' => 'info',
|
||||
]),
|
||||
Split::make([
|
||||
DatePicker::make('date')
|
||||
->required()
|
||||
->default(today()),
|
||||
DatePicker::make('due_date'),
|
||||
])->columnSpan(2),
|
||||
|
||||
ToggleButtons::make('has_pst')
|
||||
->label('PST')
|
||||
->boolean('On', 'Off')
|
||||
->default(false)
|
||||
// ->inline()
|
||||
->colors([
|
||||
'true' => 'info',
|
||||
'false' => 'info',
|
||||
]),
|
||||
ToggleButtons::make('status')
|
||||
->options(InvoiceStatus::class)
|
||||
->required()
|
||||
->inline()
|
||||
->default(InvoiceStatus::UNPAID)
|
||||
->columnSpan(2),
|
||||
|
||||
ToggleButtons::make('has_hst')
|
||||
->label('HST')
|
||||
->boolean('On', 'Off')
|
||||
->default(false)
|
||||
// ->inline()
|
||||
->colors([
|
||||
'true' => 'info',
|
||||
'false' => 'info',
|
||||
]),
|
||||
])->columnSpan(1),
|
||||
Grid::make(3)
|
||||
->schema([
|
||||
Toggle::make('has_gst')
|
||||
->label('GST')
|
||||
->inline(false)
|
||||
->default(true),
|
||||
|
||||
ToggleButtons::make('status')
|
||||
->options(InvoiceStatus::class)
|
||||
->required()
|
||||
->inline()
|
||||
->default(InvoiceStatus::UNPAID)
|
||||
->columnSpan(1),
|
||||
Toggle::make('has_pst')
|
||||
->label('PST')
|
||||
->inline(false)
|
||||
->default(false),
|
||||
|
||||
Toggle::make('has_hst')
|
||||
->label('HST')
|
||||
->inline(false)
|
||||
->default(false),
|
||||
]),
|
||||
|
||||
])->columnSpan(fn (?Invoice $record) => $record === null ? 2 : 1),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpan(2),
|
||||
->columnSpan(fn (?Invoice $record) => $record === null ? 3 : 2),
|
||||
|
||||
Section::make()
|
||||
->schema([
|
||||
@ -159,40 +150,60 @@ public static function table(Table $table): Table
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label('Created')
|
||||
->date()
|
||||
->date('Y-m-d')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('subtotal')
|
||||
->money()
|
||||
->alignRight(),
|
||||
->alignRight()
|
||||
->sortable()
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('has_gst')
|
||||
->label('GST')
|
||||
// FIXME: sortable doesn't sort correctly
|
||||
TextColumn::make('gst_amount')
|
||||
->label('GST/HST')
|
||||
->money()
|
||||
->formatStateUsing(function (Invoice $record) {
|
||||
if ($record->has_gst) {
|
||||
return '$'.number_format($record->gst_amount, 2);
|
||||
}
|
||||
|
||||
return '-';
|
||||
->getStateUsing(function (Invoice $record) {
|
||||
return $record->has_gst
|
||||
? '$'.number_format($record->gst_amount, 2)
|
||||
: ($record->has_hst ? '$'.number_format($record->hst_amount, 2) : '-');
|
||||
})
|
||||
->alignRight(),
|
||||
->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");
|
||||
// }),
|
||||
|
||||
TextColumn::make('has_pst')
|
||||
TextColumn::make('pst_amount')
|
||||
->label('PST')
|
||||
->formatStateUsing(function (Invoice $record) {
|
||||
if ($record->has_pst) {
|
||||
return '$'.number_format($record->pst_amount, 2);
|
||||
}
|
||||
|
||||
return '-';
|
||||
->getStateUsing(function (Invoice $record) {
|
||||
return $record->has_pst ? '$'.number_format($record->pst_amount, 2) : '-';
|
||||
})
|
||||
->alignRight(),
|
||||
->alignRight()
|
||||
->sortable()
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('total')
|
||||
->money()
|
||||
->alignRight()
|
||||
->weight(FontWeight::Medium)
|
||||
->sortable()
|
||||
->searchable(),
|
||||
|
||||
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(),
|
||||
@ -235,18 +246,48 @@ public static function table(Table $table): Table
|
||||
])
|
||||
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkAction::make('Mark as paid')
|
||||
->action(function (Collection $records) {
|
||||
$records->each->setStatus(InvoiceStatus::PAID);
|
||||
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);
|
||||
|
||||
Notification::make()
|
||||
->title(count($records).' item(s) saved successfully')
|
||||
->title('Payment created 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);
|
||||
@ -274,15 +315,16 @@ 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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,17 @@
|
||||
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 [
|
||||
|
@ -23,6 +23,10 @@ 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()),
|
||||
@ -37,7 +41,11 @@ public function getTabs(): array
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
Actions\CreateAction::make()
|
||||
->modal()
|
||||
->modalWidth('lg')
|
||||
->icon(IconEnum::NEW->value)
|
||||
->successRedirectUrl(fn ($record) => InvoiceResource::getUrl('edit', ['record' => $record->id])),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
@ -3,10 +3,11 @@
|
||||
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\Customer;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Order;
|
||||
use App\Models\OrderProduct;
|
||||
use App\Models\ProductService;
|
||||
@ -28,6 +29,8 @@
|
||||
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;
|
||||
@ -57,11 +60,13 @@ public static function form(Form $form): Form
|
||||
->options(OrderType::class)
|
||||
->searchable(),
|
||||
|
||||
// Split::make([
|
||||
Select::make('customer_id')
|
||||
->required()
|
||||
->label('Customer')
|
||||
->options(Customer::all()->pluck('company_name', 'id'))
|
||||
->relationship(name: 'customer', titleAttribute: 'company_name')
|
||||
->preload()
|
||||
->createOptionForm(fn ($form) => CustomerResource::form($form))
|
||||
->createOptionAction(fn ($action) => $action->modalWidth('lg'))
|
||||
->searchable(),
|
||||
|
||||
TextInput::make('customer_po')
|
||||
@ -181,6 +186,7 @@ public static function form(Form $form): Form
|
||||
->defaultItems(1),
|
||||
|
||||
Repeater::make('services')
|
||||
->view('filament.forms.compact-repeater')
|
||||
->label('Product Services')
|
||||
->schema([
|
||||
Grid::make(19)
|
||||
@ -202,13 +208,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)
|
||||
@ -343,7 +349,6 @@ public static function table(Table $table): Table
|
||||
Tables\Actions\EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
|
||||
Tables\Actions\BulkAction::make('updateStatus')
|
||||
->form([
|
||||
Select::make('status')
|
||||
@ -366,16 +371,93 @@ public static function table(Table $table): Table
|
||||
->color('info')
|
||||
->deselectRecordsAfterCompletion(),
|
||||
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
BulkActionGroup::make([
|
||||
BulkAction::make('Create individual invoices')
|
||||
->icon(IconEnum::INVOICE->value)
|
||||
->action(function (Collection $records): void {
|
||||
[$invoiced, $toInvoice] = $records->partition(fn ($record) => $record->invoice);
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
];
|
||||
$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\DeleteBulkAction::make(),
|
||||
])->label('Other actions'),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
|
@ -2,9 +2,13 @@
|
||||
|
||||
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;
|
||||
@ -13,13 +17,20 @@
|
||||
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']);
|
||||
@ -148,12 +159,11 @@ protected function getHeaderActions(): array
|
||||
Action::make('save')
|
||||
->label('Save changes')
|
||||
->action('save')
|
||||
->icon('lucide-save'),
|
||||
->icon(IconEnum::SAVE->value),
|
||||
|
||||
Actions\ReplicateAction::make()
|
||||
->label('Duplicate')
|
||||
->icon('lucide-copy')
|
||||
->color('info')
|
||||
->icon(IconEnum::COPY->value)
|
||||
->mutateRecordDataUsing(function (array $data): array {
|
||||
$po = 'Duplicate of '.$data['customer_po'];
|
||||
$data['customer_po'] = $po;
|
||||
@ -195,23 +205,37 @@ protected function getHeaderActions(): array
|
||||
})
|
||||
->successRedirectUrl(fn (Model $replica): string => OrderResource::getUrl('edit', [$replica])),
|
||||
|
||||
// Action::make('invoice')
|
||||
// ->visible(fn () => auth()->user()->is_admin)
|
||||
// ->label('To Invoice')
|
||||
// ->icon('lucide-receipt-text'),
|
||||
//
|
||||
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('print')
|
||||
->icon('lucide-printer')
|
||||
->icon(IconEnum::PRINT->value)
|
||||
->url(fn (Order $record) => route('orders.pdf', $record))
|
||||
->openUrlInNewTab(),
|
||||
|
||||
Actions\DeleteAction::make()
|
||||
->icon('lucide-trash-2'),
|
||||
->icon(IconEnum::TRASH->value),
|
||||
];
|
||||
}
|
||||
|
||||
// protected function getRedirectUrl(): string
|
||||
// {
|
||||
// return $this->previousUrl ?? $this->getResource()::getUrl('index');
|
||||
// }
|
||||
}
|
||||
|
@ -15,77 +15,69 @@ 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(),
|
||||
Actions\CreateAction::make()
|
||||
->icon(IconEnum::NEW->value),
|
||||
];
|
||||
}
|
||||
|
||||
public function getTabs(): array
|
||||
{
|
||||
return [
|
||||
'all' => Tab::make('All')
|
||||
->icon(IconEnum::TAB_ALL->value),
|
||||
|
||||
'active' => Tab::make()
|
||||
->query(function ($query) {
|
||||
return $query
|
||||
->whereNot('status', OrderStatus::INVOICED)
|
||||
->whereNot('status', ORderStatus::SHIPPED);
|
||||
})
|
||||
->query(fn ($query) => $this->excludeStatuses($query))
|
||||
->icon(OrderStatus::PRODUCTION->getIcon())
|
||||
->badge(function () {
|
||||
return Order::whereNot('status', OrderStatus::SHIPPED)
|
||||
->whereNot('status', OrderStatus::INVOICED)
|
||||
->count();
|
||||
}),
|
||||
->badge(fn () => $this->getBadgeCount(fn ($query) => $this->excludeStatuses($query))),
|
||||
|
||||
'unprinted' => Tab::make()
|
||||
->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;
|
||||
})
|
||||
->query(fn ($query) => $this->excludeStatuses($query)->where('printed', false))
|
||||
->icon(IconEnum::PRINT->value)
|
||||
->badge(fn () => $this->getBadgeCount(fn ($query) => $query->where('printed', false)))
|
||||
->badgeColor('success'),
|
||||
|
||||
'overdue' => Tab::make()
|
||||
->query(function ($query) {
|
||||
return $query->whereDate('due_date', '<=', today())
|
||||
->whereNot('status', OrderStatus::INVOICED)
|
||||
->whereNot('status', ORderStatus::SHIPPED);
|
||||
})
|
||||
->query(fn ($query) => $this->excludeStatuses($query)->whereDate('due_date', '<=', today()))
|
||||
->icon(IconEnum::TAB_OVERDUE->value)
|
||||
->badge(function () {
|
||||
$count = Order::whereDate('due_date', '<=', today())
|
||||
->whereNot('status', OrderStatus::INVOICED)
|
||||
->whereNot('status', ORderStatus::SHIPPED)
|
||||
->count();
|
||||
|
||||
return $count > 0 ? $count : null;
|
||||
})
|
||||
->badge(fn () => $this->getBadgeCount(fn ($query) => $query->whereDate('due_date', '<=', today())))
|
||||
->badgeColor('danger'),
|
||||
|
||||
'rush' => Tab::make()
|
||||
->query(function ($query) {
|
||||
return $query->where('rush', true)
|
||||
->whereNot('status', OrderStatus::INVOICED)
|
||||
->whereNot('status', OrderStatus::SHIPPED);
|
||||
})
|
||||
->query(fn ($query) => $this->excludeStatuses($query)->where('rush', true))
|
||||
->icon(OrderAttributes::rush->getIcon())
|
||||
->badge(function () {
|
||||
$count = Order::where('rush', true)
|
||||
->whereNot('status', OrderStatus::INVOICED)
|
||||
->whereNot('status', OrderStatus::SHIPPED)
|
||||
->count();
|
||||
|
||||
return $count > 0 ? $count : null;
|
||||
})
|
||||
->badge(fn () => $this->getBadgeCount(fn ($query) => $query->where('rush', true)))
|
||||
->badgeColor('warning'),
|
||||
|
||||
'all' => Tab::make('All')
|
||||
->icon(IconEnum::TAB_ALL->value),
|
||||
'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()),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\OrderResource\RelationManagers;
|
||||
|
||||
use App\Enums\IconEnum;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
@ -35,15 +36,18 @@ public function table(Table $table): Table
|
||||
//
|
||||
])
|
||||
->headerActions([
|
||||
Tables\Actions\CreateAction::make(),
|
||||
Tables\Actions\CreateAction::make()
|
||||
->icon(IconEnum::NEW->value),
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
Tables\Actions\DeleteAction::make(),
|
||||
Tables\Actions\DeleteAction::make()
|
||||
->icon(IconEnum::TRASH->value),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
Tables\Actions\DeleteBulkAction::make()
|
||||
->icon(IconEnum::TRASH->value),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
@ -46,7 +46,6 @@ 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')
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
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;
|
||||
@ -13,7 +14,8 @@ class EditPackingSlip extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\DeleteAction::make()
|
||||
->icon(IconEnum::TRASH->value),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
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;
|
||||
@ -13,7 +14,8 @@ class ListPackingSlips extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
Actions\CreateAction::make()
|
||||
->icon(IconEnum::NEW->value),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -2,16 +2,20 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources;
|
||||
|
||||
use App\Filament\Admin\Resources\CustomerResource\RelationManagers\PaymentsRelationManager;
|
||||
use App\Enums\IconEnum;
|
||||
use App\Filament\Admin\Resources\InvoiceResource\Pages\ListInvoices;
|
||||
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;
|
||||
|
||||
@ -19,7 +23,7 @@ class PaymentResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Payment::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'lucide-hand-coins';
|
||||
protected static ?string $navigationIcon = IconEnum::PAYMENTS->value;
|
||||
|
||||
protected static ?string $navigationGroup = 'Financial';
|
||||
|
||||
@ -30,61 +34,108 @@ public static function form(Form $form): Form
|
||||
return $form
|
||||
->schema([
|
||||
Section::make([
|
||||
Select::make('customer_id')
|
||||
->relationship('customer', 'company_name')
|
||||
->required()
|
||||
->searchable()
|
||||
->preload(),
|
||||
TextInput::make('amount')
|
||||
->required()
|
||||
->minValue(0)
|
||||
->maxValue(99999999)
|
||||
->numeric(),
|
||||
Textarea::make('notes'),
|
||||
]),
|
||||
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),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
public static function table(Table $table, ?bool $showSearchable = true): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('created_at')
|
||||
->label('Date')
|
||||
->date('Y-m-d')
|
||||
->searchable(),
|
||||
->searchable($showSearchable),
|
||||
|
||||
TextColumn::make('customer.company_name')
|
||||
->hidden(fn ($livewire) => $livewire::class === PaymentsRelationManager::class)
|
||||
->searchable(),
|
||||
->hidden(fn ($livewire) => $livewire::class !== Pages\ListPayments::class)
|
||||
->searchable($showSearchable),
|
||||
|
||||
TextColumn::make('notes')
|
||||
->limit(100)
|
||||
TextColumn::make('check_number')
|
||||
->searchable($showSearchable)
|
||||
->extraHeaderAttributes(['class' => 'w-full']),
|
||||
|
||||
TextColumn::make('amount')
|
||||
->searchable()
|
||||
->numeric()
|
||||
->searchable($showSearchable)
|
||||
->alignRight()
|
||||
->money(),
|
||||
|
||||
TextColumn::make('unapplied_amount')
|
||||
->searchable($showSearchable)
|
||||
->label('Balance')
|
||||
->alignRight()
|
||||
->money(),
|
||||
])
|
||||
->actions([
|
||||
ViewAction::make(),
|
||||
\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(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()->is_admin;
|
||||
return auth()->user()->is_admin ?? false;
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
InvoicesRelationManager::class,
|
||||
];
|
||||
}
|
||||
|
||||
@ -92,9 +143,7 @@ public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListPayments::route('/'),
|
||||
// 'view' => Pages\ViewPayment::route('/{record}'),
|
||||
// 'create' => Pages\CreatePayment::route('/create'),
|
||||
// 'edit' => Pages\EditPayment::route('/{record}/edit'),
|
||||
'edit' => Pages\EditPayment::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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,9 +26,12 @@ protected function getHeaderActions(): array
|
||||
->body('Payments have been distributed')
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
}),*/
|
||||
|
||||
Actions\CreateAction::make(),
|
||||
Actions\CreateAction::make()
|
||||
->modalWidth('lg')
|
||||
->icon(IconEnum::NEW->value)
|
||||
->successRedirectUrl(fn ($record) => PaymentResource::getUrl('edit', ['record' => $record->id])),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
<?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])),
|
||||
]);
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
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;
|
||||
@ -36,24 +37,28 @@ public static function form(Form $form): Form
|
||||
Grid::make(3)
|
||||
->schema([
|
||||
Section::make([
|
||||
Select::make('customer_id')
|
||||
->required()
|
||||
->label('Customer')
|
||||
->options(Customer::all()->pluck('company_name', 'id'))
|
||||
->reactive()
|
||||
->searchable()
|
||||
->columnSpan(1),
|
||||
Group::make([
|
||||
|
||||
DatePicker::make('date')
|
||||
->default(today())
|
||||
->required(),
|
||||
Select::make('customer_id')
|
||||
->required()
|
||||
->label('Customer')
|
||||
->options(Customer::all()->pluck('company_name', 'id'))
|
||||
->reactive()
|
||||
->searchable()
|
||||
->columnSpan(1),
|
||||
|
||||
TextArea::make('notes')
|
||||
->rows(3)
|
||||
->columnSpan(2),
|
||||
DatePicker::make('date')
|
||||
->default(today())
|
||||
->required(),
|
||||
|
||||
TextArea::make('notes')
|
||||
->rows(3)
|
||||
->columnSpan(2),
|
||||
]),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpan(fn (?Quote $record) => $record === null ? 3 : 2),
|
||||
->columnSpan(fn (?Quote $record) => $record === null ? 3 : 2)
|
||||
->extraAttributes(['class' => 'h-full']),
|
||||
|
||||
Section::make()
|
||||
->schema([
|
||||
@ -69,7 +74,8 @@ public static function form(Form $form): Form
|
||||
|
||||
])
|
||||
->columnSpan(1)
|
||||
->hidden(fn (?Quote $record) => $record === null),
|
||||
->hidden(fn (?Quote $record) => $record === null)
|
||||
->extraAttributes(['class' => 'h-full']),
|
||||
]),
|
||||
|
||||
TableRepeater::make('embroideryEntries')
|
||||
@ -115,30 +121,24 @@ public static function form(Form $form): Form
|
||||
TextInput::make('logo')
|
||||
->label('Logo name')
|
||||
->columnSpan(2),
|
||||
TextInput::make('placement'),
|
||||
TextInput::make('quantity')
|
||||
->rules(['numeric']),
|
||||
->rules(['numeric'])
|
||||
->label('Qty'),
|
||||
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,19 +147,33 @@ public static function form(Form $form): Form
|
||||
->rules('numeric'),
|
||||
TextInput::make('run_charge')
|
||||
->rules('numeric'),
|
||||
TextInput::make('other_charges')
|
||||
TextInput::make('artwork_fee')
|
||||
->label('Artwork fee')
|
||||
->rules('numeric'),
|
||||
TextInput::make('repacking_fee')
|
||||
->label('Repack. fee')
|
||||
->rules('numeric'),
|
||||
])
|
||||
->addActionLabel('Add Screen Print Entry')
|
||||
->defaultItems(0)
|
||||
->reorderable()
|
||||
->colStyles([
|
||||
'logo' => 'width: 15%',
|
||||
'quantity' => 'width: 5%',
|
||||
'width' => 'width: 6%',
|
||||
'height' => 'width: 6%',
|
||||
'setup_amount' => 'width: 5%',
|
||||
'color_amount' => 'width: 5%',
|
||||
'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%',
|
||||
|
||||
]),
|
||||
|
||||
TableRepeater::make('heatTransferEntries')
|
||||
@ -167,6 +181,7 @@ public static function form(Form $form): Form
|
||||
->schema([
|
||||
TextInput::make('logo')
|
||||
->label('Logo name'),
|
||||
TextInput::make('placement'),
|
||||
TextInput::make('quantity')
|
||||
->prefix('#')
|
||||
->rules('numeric'),
|
||||
@ -184,11 +199,12 @@ public static function form(Form $form): Form
|
||||
->defaultItems(0)
|
||||
->reorderable()
|
||||
->colStyles([
|
||||
'logo' => 'width: 20%',
|
||||
'quantity' => 'width: 10%',
|
||||
'width' => 'width: 11%',
|
||||
'height' => 'width: 11%',
|
||||
'price' => 'width: 15%',
|
||||
'logo' => 'width: 25%',
|
||||
'placement' => 'width: 25%',
|
||||
'quantity' => 'width: 10%',
|
||||
'width' => 'width: 11%',
|
||||
'height' => 'width: 11%',
|
||||
'price' => 'width: 15%',
|
||||
]),
|
||||
|
||||
])->columns(1);
|
||||
@ -202,6 +218,8 @@ public static function table(Table $table): Table
|
||||
->color('primary')
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('internal_id'),
|
||||
|
||||
TextColumn::make('date')
|
||||
->date('Y-m-d')
|
||||
->sortable()
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\QuoteResource\Pages;
|
||||
|
||||
use App\Enums\IconEnum;
|
||||
use App\Filament\Admin\Resources\QuoteResource;
|
||||
use App\Models\Quote;
|
||||
use Filament\Actions;
|
||||
@ -12,20 +13,26 @@ 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('lucide-save'),
|
||||
->icon(IconEnum::SAVE->value),
|
||||
|
||||
Action::make('print')
|
||||
->icon('lucide-printer')
|
||||
->icon(IconEnum::PRINT->value)
|
||||
->url(fn (Quote $record) => route('pdf.quote', $record))
|
||||
->openUrlInNewTab(),
|
||||
|
||||
Actions\DeleteAction::make(),
|
||||
Actions\DeleteAction::make()
|
||||
->icon(IconEnum::TRASH->value),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
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;
|
||||
@ -13,7 +14,8 @@ class ListQuotes extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
Actions\CreateAction::make()
|
||||
->icon(IconEnum::NEW->value),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@
|
||||
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;
|
||||
|
||||
@ -42,44 +44,67 @@ public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
TextColumn::make('name')
|
||||
->label('Code'),
|
||||
|
||||
Tables\Columns\TextColumn::make('value')
|
||||
TextColumn::make('value')
|
||||
->label('Long Name')
|
||||
->extraHeaderAttributes(['class' => 'w-full']),
|
||||
|
||||
Tables\Columns\TextColumn::make('quantity')
|
||||
TextColumn::make('productServices.amount')
|
||||
->label('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')
|
||||
)
|
||||
),
|
||||
|
||||
Tables\Columns\TextColumn::make('amount')
|
||||
TextColumn::make('productServices.amount_price')
|
||||
->label('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']
|
||||
);
|
||||
})
|
||||
->money('usd'),
|
||||
->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')
|
||||
)
|
||||
),
|
||||
|
||||
Tables\Columns\TextColumn::make('salesPercentage')
|
||||
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'),
|
||||
}),
|
||||
|
||||
Tables\Columns\TextColumn::make('averagePrice')
|
||||
TextColumn::make('averagePrice')
|
||||
->getStateUsing(function (Table $table, Model $record) {
|
||||
return $record->getAveragePriceAttribute(
|
||||
$table->getFilter('created_at')->getState()['created_at'],
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
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;
|
||||
@ -13,7 +14,8 @@ class ListShippingEntries extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
Actions\CreateAction::make()
|
||||
->icon(IconEnum::NEW->value),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@
|
||||
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;
|
||||
@ -36,24 +35,8 @@ 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
|
||||
@ -70,7 +53,8 @@ public static function table(Table $table): Table
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make(),
|
||||
Tables\Actions\EditAction::make()
|
||||
->modalWidth('xs'),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
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;
|
||||
@ -13,7 +14,10 @@ class ListTaxRates extends ListRecords
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
Actions\CreateAction::make()
|
||||
->modalWidth('xs')
|
||||
->icon(IconEnum::NEW->value)
|
||||
->createAnother(false),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
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;
|
||||
@ -36,32 +35,29 @@ public static function form(Form $form): Form
|
||||
->autocomplete('new-username')
|
||||
->unique()
|
||||
->required()
|
||||
->columnSpan(1),
|
||||
->columnSpanFull(),
|
||||
|
||||
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')
|
||||
->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'),
|
||||
])
|
||||
->columnSpan(2),
|
||||
TextInput::make('password_verify')
|
||||
->label('Verify password')
|
||||
->password()
|
||||
->revealable()
|
||||
->same('password')
|
||||
->dehydrated(false)
|
||||
->required(fn (string $operation) => $operation === 'create'),
|
||||
]),
|
||||
|
||||
Section::make('Permissions')
|
||||
->description('Administrators can access financial information and change settings.')
|
||||
->description('Administrators can access invoices and settings')
|
||||
->schema([
|
||||
Toggle::make('is_admin')
|
||||
->columnSpanFull()
|
||||
->label('User is an administrator')
|
||||
->reactive()
|
||||
->afterStateUpdated(fn ($state, callable $set) => $set('customer_id', null))
|
||||
@ -70,7 +66,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')
|
||||
@ -102,7 +98,9 @@ public static function table(Table $table): Table
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
Tables\Actions\EditAction::make()->modal(),
|
||||
Tables\Actions\EditAction::make()
|
||||
->modalWidth('md')
|
||||
->modal(),
|
||||
])
|
||||
->defaultSort('customer_id', 'asc');
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
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;
|
||||
@ -14,6 +15,8 @@ protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make()
|
||||
->modalWidth('md')
|
||||
->icon(IconEnum::NEW->value)
|
||||
->modal(),
|
||||
];
|
||||
}
|
||||
|
@ -1,34 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
@ -35,7 +35,9 @@ 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();
|
||||
}
|
||||
@ -43,6 +45,8 @@ 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()])
|
||||
@ -65,6 +69,8 @@ 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())
|
||||
|
@ -1,33 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
<?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'),
|
||||
]);
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ class HeatTransferEntry extends Model
|
||||
|
||||
protected $fillable = [
|
||||
'quote_id',
|
||||
'placement',
|
||||
'quantity',
|
||||
'logo',
|
||||
'width',
|
||||
|
@ -5,6 +5,7 @@
|
||||
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;
|
||||
@ -55,6 +56,19 @@ 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');
|
||||
|
@ -17,8 +17,11 @@ class InvoiceReport extends Model
|
||||
'customer_id',
|
||||
'date_start',
|
||||
'date_end',
|
||||
'filter_paid',
|
||||
'subtotal',
|
||||
'with_unpaid',
|
||||
'with_partially_paid',
|
||||
'with_paid',
|
||||
'with_void',
|
||||
'pst',
|
||||
'gst',
|
||||
];
|
||||
@ -38,25 +41,26 @@ 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->filter_paid, function ($query) {
|
||||
$query->where('status', InvoiceStatus::UNPAID);
|
||||
});
|
||||
->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));
|
||||
|
||||
$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');
|
||||
|
@ -9,6 +9,7 @@
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
#[ObservedBy(PaymentObserver::class)]
|
||||
|
||||
@ -18,21 +19,23 @@ class Payment extends Model
|
||||
|
||||
protected $fillable = [
|
||||
'customer_id',
|
||||
'check_number',
|
||||
'date',
|
||||
'amount',
|
||||
'unapplied_amount',
|
||||
'notes',
|
||||
];
|
||||
|
||||
public function applyToInvoices(): void
|
||||
public function applyToInvoices(Collection $invoices): void
|
||||
{
|
||||
$remaining = $this->unapplied_amount ?? $this->amount;
|
||||
|
||||
$invoices = Invoice::where('customer_id', $this->customer_id)
|
||||
->where('status', InvoiceStatus::UNPAID)
|
||||
->orderBy('date')
|
||||
->get();
|
||||
$filteredInvoices = $invoices->whereIn('status', [
|
||||
InvoiceStatus::UNPAID,
|
||||
InvoiceStatus::PARTIALLY_PAID,
|
||||
]);
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
foreach ($filteredInvoices as $invoice) {
|
||||
$balance = $invoice->remainingBalance();
|
||||
|
||||
if ($remaining <= 0) {
|
||||
@ -47,7 +50,7 @@ public function applyToInvoices(): void
|
||||
if ($invoice->remainingBalance() == 0) {
|
||||
$invoice->setStatus(InvoiceStatus::PAID);
|
||||
} elseif ($applied > 0) {
|
||||
$invoice->setStatus(InvoiceStatus::UNPAID);
|
||||
$invoice->setStatus(InvoiceStatus::PARTIALLY_PAID);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,16 +2,21 @@
|
||||
|
||||
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',
|
||||
@ -30,15 +35,11 @@ public function getTotalAttribute(): float
|
||||
$embDigitizingTotal = $this->embroideryEntries()->sum('digitizing_cost');
|
||||
$embRunChargeTotal = $this->embroideryEntries()->sum('run_charge');
|
||||
|
||||
$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');
|
||||
$scpTotal = $this->screenPrintEntries->sum(fn (ScreenPrintEntry $record) => $record->total_price);
|
||||
|
||||
$heatTransferTotal = $this->heatTransferEntries()->sum('price');
|
||||
|
||||
return $embDigitizingTotal + $embRunChargeTotal + $scpRunChargeTotal + $scpOtherChargeTotal + $scpFleeceTotal + $scpFlashTotal + $scpPolyInkTotal + $heatTransferTotal;
|
||||
return $embDigitizingTotal + $embRunChargeTotal + $scpTotal + $heatTransferTotal;
|
||||
}
|
||||
|
||||
public function customer(): BelongsTo
|
||||
|
@ -14,6 +14,7 @@ class ScreenPrintEntry extends Model
|
||||
'quote_id',
|
||||
'quantity',
|
||||
'logo',
|
||||
'placement',
|
||||
'width',
|
||||
'height',
|
||||
'color_amount',
|
||||
@ -24,7 +25,8 @@ class ScreenPrintEntry extends Model
|
||||
'flash',
|
||||
'fleece',
|
||||
'poly_ink',
|
||||
'other_charges',
|
||||
'artwork_fee',
|
||||
'repacking_fee',
|
||||
'notes',
|
||||
];
|
||||
|
||||
@ -34,7 +36,9 @@ class ScreenPrintEntry extends Model
|
||||
|
||||
protected function getTotalPriceAttribute(): float
|
||||
{
|
||||
return $this->flash + $this->fleece + $this->poly_ink + $this->run_charge + $this->other_charges;
|
||||
$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;
|
||||
}
|
||||
|
||||
public function quote(): BelongsTo
|
||||
|
@ -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_until', '<=', $created_until));
|
||||
->when($created_until, fn ($query) => $query->whereDate('created_at', '<=', $created_until));
|
||||
|
||||
$total = $query->count();
|
||||
|
||||
|
@ -3,13 +3,15 @@
|
||||
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 HasName
|
||||
class User extends Authenticatable implements FilamentUser, HasName
|
||||
{
|
||||
/** @use HasFactory<UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
@ -48,6 +50,11 @@ protected function casts(): array
|
||||
];
|
||||
}
|
||||
|
||||
public function canAccessPanel(Panel $panel): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setIsAdminAttribute(bool $value): void
|
||||
{
|
||||
$this->attributes['is_admin'] = $value;
|
||||
|
@ -23,7 +23,8 @@ public function creating(Invoice $invoice): void
|
||||
public function created(Invoice $invoice): void
|
||||
{
|
||||
|
||||
$invoice->internal_id = 'INV4'.str_pad($invoice->id, 4, '0', STR_PAD_LEFT);
|
||||
// $invoice->internal_id = 'TN4'.str_pad($invoice->id, 4, '0', STR_PAD_LEFT);
|
||||
$invoice->internal_id = 'TN'.$invoice->id + 4000;
|
||||
$invoice->saveQuietly();
|
||||
|
||||
$invoice->calculateTotals();
|
||||
|
@ -12,7 +12,7 @@ class OrderObserver
|
||||
*/
|
||||
public function created(Order $order): void
|
||||
{
|
||||
if ($order->invoice()->exists()) {
|
||||
if ($order->invoice != null) {
|
||||
$order->invoice->calculateTotals();
|
||||
}
|
||||
}
|
||||
@ -22,7 +22,7 @@ public function created(Order $order): void
|
||||
*/
|
||||
public function updated(Order $order): void
|
||||
{
|
||||
if ($order->invoice()->exists()) {
|
||||
if ($order->invoice != null) {
|
||||
$order->invoice->calculateTotals();
|
||||
}
|
||||
}
|
||||
@ -33,7 +33,9 @@ public function updated(Order $order): void
|
||||
public function saved(Order $order): void
|
||||
{
|
||||
if ($order->isDirty(['invoice_id']) && Invoice::where('id', $order->invoice_id)->exists()) {
|
||||
$order->invoice->calculateTotals();
|
||||
if ($order->invoice != null) {
|
||||
$order->invoice->calculateTotals();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,7 +44,7 @@ public function saved(Order $order): void
|
||||
*/
|
||||
public function deleted(Order $order): void
|
||||
{
|
||||
if ($order->invoice()->exists()) {
|
||||
if ($order->invoice != null) {
|
||||
$order->invoice->calculateTotals();
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ class PaymentObserver
|
||||
*/
|
||||
public function saved(Payment $payment): void
|
||||
{
|
||||
$payment->applyToInvoices();
|
||||
// $payment->applyToInvoices();
|
||||
}
|
||||
|
||||
/**
|
||||
|
17
app/Observers/QuoteObserver.php
Normal file
17
app/Observers/QuoteObserver.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
@ -18,8 +17,5 @@ public function register(): void
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
Paginator::useBootstrapFive();
|
||||
}
|
||||
public function boot(): void {}
|
||||
}
|
||||
|
@ -29,9 +29,10 @@ public function panel(Panel $panel): Panel
|
||||
->path('admin')
|
||||
->login(UsernameLogin::class)
|
||||
->colors([
|
||||
'primary' => Color::Blue,
|
||||
'code' => Color::hex('#d63384'),
|
||||
'invoiced' => Color::hex('#900090'),
|
||||
'primary' => Color::Blue,
|
||||
'code' => Color::hex('#d63384'),
|
||||
'invoicing' => Color::hex('#DD00DD'),
|
||||
'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')
|
||||
|
@ -14,11 +14,11 @@
|
||||
"laravel/tinker": "^2.9",
|
||||
"livewire/livewire": "^3.5",
|
||||
"mallardduck/blade-lucide-icons": "^1.23",
|
||||
"spatie/laravel-pdf": "^1.5"
|
||||
"spatie/laravel-pdf": "^1.5",
|
||||
"fakerphp/faker": "^1.23"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-ide-helper": "^3.5",
|
||||
"fakerphp/faker": "^1.23",
|
||||
"larastan/larastan": "^2.0",
|
||||
"laravel/pint": "^1.17",
|
||||
"laravel/sail": "^1.26",
|
||||
|
1121
composer.lock
generated
1121
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -15,6 +15,18 @@
|
||||
|
||||
'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
|
||||
|
@ -17,11 +17,12 @@ class HeatTransferEntryFactory extends Factory
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'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),
|
||||
'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),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -14,12 +14,15 @@ 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(),
|
||||
'filter_paid' => $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(),
|
||||
'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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -19,17 +19,19 @@ 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),
|
||||
'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),
|
||||
'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),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,8 @@ class TaxRateFactory extends Factory
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
'name' => strtoupper($this->faker->randomLetter()).'ST',
|
||||
'value' => random_int(1, 15),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ public function up(): void
|
||||
$table->id();
|
||||
|
||||
$table->foreignId('customer_id')->constrained();
|
||||
$table->string('internal_id')->nullable();
|
||||
$table->date('date');
|
||||
$table->longText('notes')->nullable();
|
||||
|
||||
|
@ -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('filter_paid');
|
||||
$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->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();
|
||||
});
|
||||
|
@ -15,6 +15,8 @@ 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();
|
||||
|
@ -18,17 +18,19 @@ 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->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->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->text('notes')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
|
@ -17,6 +17,7 @@ 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();
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
@ -34,9 +33,5 @@ public function run(): void
|
||||
InvoiceReportSeeder::class,
|
||||
]);
|
||||
|
||||
User::factory()->create([
|
||||
'username' => 'admin',
|
||||
'is_admin' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
22
database/seeders/DefaultUserSeeder.php
Normal file
22
database/seeders/DefaultUserSeeder.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
@ -13,6 +13,11 @@ 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])),
|
||||
|
91
deploy/Dockerfile
Normal file
91
deploy/Dockerfile
Normal file
@ -0,0 +1,91 @@
|
||||
# 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"]
|
||||
|
63
deploy/docker-compose.yml
Normal file
63
deploy/docker-compose.yml
Normal file
@ -0,0 +1,63 @@
|
||||
# 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
|
||||
|
35
deploy/nginx.conf
Normal file
35
deploy/nginx.conf
Normal file
@ -0,0 +1,35 @@
|
||||
# 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;
|
||||
}
|
||||
}
|
||||
|
0
deploy/php.ini
Normal file
0
deploy/php.ini
Normal file
@ -1,131 +0,0 @@
|
||||
// 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 +0,0 @@
|
||||
Subproject commit 65fe7885813f3d4dc87f0fc52495247592fe276c
|
@ -1 +0,0 @@
|
||||
ERROR [mprocs::error] Error: channel closed
|
@ -1,5 +0,0 @@
|
||||
procs:
|
||||
sail:
|
||||
shell: "/home/nisse/Code/topnotch_website/vendor/bin/sail up"
|
||||
npm:
|
||||
shell: "npm run dev"
|
597
package-lock.json
generated
597
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -13,10 +13,11 @@
|
||||
"laravel-vite-plugin": "^1.0",
|
||||
"postcss": "^8.4.47",
|
||||
"sass": "^1.56.1",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"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.
@ -1,21 +0,0 @@
|
||||
{
|
||||
"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
Loading…
x
Reference in New Issue
Block a user